import { describe, expect, it } from 'vitest';
import { ENEMY_KINDS } from '../../../data/enemies';
import { EnemyBrain, type EnemySenses } from '../EnemyBrain';

/** Тик ИИ на 1/60 с; возвращает последнее намерение. */
const run = (b: EnemyBrain, seconds: number, senses: EnemySenses): ReturnType<EnemyBrain['update']> => {
    let intent: ReturnType<EnemyBrain['update']> = { type: 'idle' };
    for (let i = 0; i < Math.round(seconds * 60); i++) intent = b.update(1 / 60, senses);
    return intent;
};

/** Прогон с ловлей конкретного намерения (strike/shoot выдаются одним тиком). */
const sawIntent = (b: EnemyBrain, seconds: number, senses: EnemySenses, type: string): boolean => {
    for (let i = 0; i < Math.round(seconds * 60); i++) {
        if (b.update(1 / 60, senses).type === type) return true;
    }
    return false;
};

const away: EnemySenses = { dist: 3.1, dirToPlayer: { x: 0, y: 1 } };
const close: EnemySenses = { dist: 0.25, dirToPlayer: { x: 1, y: 0 } };

/** Разбудить и дождаться погони (спит -> rise 0.6 с -> chase). */
const wakeUp = (b: EnemyBrain, senses: EnemySenses): void => {
    b.hearLoud();
    run(b, 0.7, senses);
};

describe('EnemyBrain (ползун)', () => {
    it('стартует спящим в пепле и не двигается', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        expect(b.asleep).toBe(true);
        expect(b.update(1 / 60, close).type).toBe('idle');
    });

    it('гулкий звук поднимает: rise -> chase', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        b.hearLoud();
        expect(b.state).toBe('rise');
        run(b, 0.7, away);
        expect(b.state).toBe('chase');
        // Вдали от героя — движение к нему
        expect(run(b, 0.05, away)).toEqual({ type: 'move', dir: { x: 0, y: 1 } });
    });

    it('в радиусе атаки бьёт контактом по кулдауну', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        wakeUp(b, away);
        expect(sawIntent(b, 0.1, close, 'strike')).toBe(true);
        // Кулдаун: в следующее окно удара нет, после — снова есть
        expect(sawIntent(b, 0.1, close, 'strike')).toBe(false);
        run(b, ENEMY_KINDS.crawler.attackCooldown, away);
        expect(sawIntent(b, 0.1, close, 'strike')).toBe(true);
    });

    it('урон станит и возвращает в погоню', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        wakeUp(b, away);
        b.hurt();
        expect(b.state).toBe('hurt');
        expect(b.update(1 / 60, away).type).toBe('idle');
        run(b, 0.3, away);
        expect(b.state).toBe('chase');
    });

    it('резонанс усыпляет на заданное время, потом просыпается', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        wakeUp(b, away);
        b.putToSleep(4);
        expect(b.asleep).toBe(true);
        expect(run(b, 2, away).type).toBe('idle');
        expect(b.asleep).toBe(true);
        run(b, 2.2, away);
        expect(b.state).toBe('rise'); // проснулся — сначала подъём
        run(b, 0.7, away);
        expect(b.state).toBe('chase');
        expect(b.asleep).toBe(false);
    });

    it('смерть окончательна: повторный die возвращает false', () => {
        const b = new EnemyBrain(ENEMY_KINDS.crawler);
        expect(b.die()).toBe(true);
        expect(b.dead).toBe(true);
        expect(b.die()).toBe(false);
        // Урон и сон мёртвого не трогают
        b.hurt();
        b.putToSleep(4);
        expect(b.dead).toBe(true);
    });
});

describe('EnemyBrain (наст)', () => {
    it('перед ударом есть замах 0.6 с', () => {
        const b = new EnemyBrain(ENEMY_KINDS.heavy);
        wakeUp(b, away);
        // Сразу — только замах, урона нет
        run(b, 0.05, close);
        expect(b.state).toBe('windup');
        run(b, 0.3, close);
        expect(b.state).toBe('windup');
        // Через 0.6 с замаха — удар и фаза атаки
        run(b, 0.35, close);
        expect(b.state).toBe('attack');
        expect(sawIntent(b, 0.05, close, 'strike')).toBe(false); // уже прошёл
    });
});

describe('EnemyBrain (плевун)', () => {
    it('сближение — отходит, издали — идёт ближе', () => {
        const b = new EnemyBrain(ENEMY_KINDS.spitter);
        wakeUp(b, { dist: 3.1, dirToPlayer: { x: 1, y: 0 } });
        expect(b.state).toBe('chase');
        // Слишком близко к keepDistance — пятится
        const back = run(b, 0.05, { dist: 1.3, dirToPlayer: { x: 1, y: 0 } });
        expect(back.type).toBe('move');
        if (back.type === 'move') expect(back.dir.x).toBeLessThan(0);
        // Слишком далеко — подходит
        const toward = run(b, 0.05, { dist: 3.8, dirToPlayer: { x: 0, y: 1 } });
        expect(toward.type).toBe('move');
        if (toward.type === 'move') expect(toward.dir.y).toBeGreaterThan(0);
    });

    it('на дистанции атаки — замах и плевок', () => {
        const b = new EnemyBrain(ENEMY_KINDS.spitter);
        wakeUp(b, { dist: 100, dirToPlayer: { x: 1, y: 0 } });
        const mid: EnemySenses = { dist: 2.5, dirToPlayer: { x: 1, y: 0 } };
        // Входим в радиус атаки — замах
        run(b, 0.05, mid);
        expect(b.state).toBe('windup');
        // Через windup — плевок (ловим намерение в тик срабатывания)
        expect(sawIntent(b, 0.45, mid, 'shoot')).toBe(true);
    });
});