import { describe, expect, it } from 'vitest';

import { ConstantRNG, RandUtil } from '@sammo-ts/common';

import type { City, General, Nation } from '../src/domain/entities.js';
import type { UnitSetDefinition } from '../src/world/types.js';
import { ActionLogger } from '../src/logging/actionLogger.js';
import { WarActionPipeline } from '../src/war/actions.js';
import { resolveWarBattle } from '../src/war/engine.js';
import type { WarEngineConfig } from '../src/war/types.js';
import { WarCrewType } from '../src/war/crewType.js';
import { loadWarTriggerModules } from '../src/war/triggers/index.js';
import { WarUnitCity, WarUnitGeneral } from '../src/war/units.js';
import {
    createItemActionModules,
    createItemModuleRegistry,
    equipNewItem,
    getEquippedItemInstance,
    loadItemModules,
} from '../src/items/index.js';

const buildConfig = (): WarEngineConfig => ({
    armPerPhase: 500,
    maxTrainByCommand: 100,
    maxAtmosByCommand: 100,
    maxTrainByWar: 110,
    maxAtmosByWar: 150,
    castleCrewTypeId: 999,
    armTypes: {
        footman: 1,
        wizard: 4,
        siege: 5,
        misc: 6,
        castle: 9,
    },
});

const buildUnitSet = (): UnitSetDefinition => ({
    id: 'test',
    name: 'test',
    crewTypes: [
        {
            id: 100,
            armType: 1,
            name: '보병',
            attack: 100,
            defence: 100,
            speed: 7,
            avoid: 10,
            magicCoef: 0,
            cost: 9,
            rice: 9,
            requirements: [],
            attackCoef: {},
            defenceCoef: {},
            info: [],
            initSkillTrigger: null,
            phaseSkillTrigger: null,
            iActionList: null,
        },
        {
            id: 999,
            armType: 9,
            name: '성벽',
            attack: 0,
            defence: 0,
            speed: 1,
            avoid: 0,
            magicCoef: 0,
            cost: 0,
            rice: 0,
            requirements: [],
            attackCoef: {},
            defenceCoef: {},
            info: [],
            initSkillTrigger: null,
            phaseSkillTrigger: null,
            iActionList: null,
        },
    ],
});

const buildNation = (): Nation => ({
    id: 1,
    name: 'TestNation',
    color: '#000000',
    capitalCityId: 1,
    chiefGeneralId: null,
    gold: 1000,
    rice: 1000,
    power: 0,
    level: 1,
    typeCode: 'test',
    meta: {
        tech: 3000,
    },
});

const buildCity = (): City => ({
    id: 1,
    name: 'TestCity',
    nationId: 1,
    level: 2,
    state: 0,
    population: 10000,
    populationMax: 10000,
    agriculture: 500,
    agricultureMax: 1000,
    commerce: 500,
    commerceMax: 1000,
    security: 500,
    securityMax: 1000,
    defence: 100,
    defenceMax: 200,
    supplyState: 1,
    frontState: 0,
    wall: 100,
    wallMax: 200,
    meta: {},
});

const buildGeneral = (strength: number): General => ({
    id: 1,
    name: 'Tester',
    nationId: 1,
    cityId: 1,
    troopId: 0,
    stats: {
        leadership: 70,
        strength,
        intelligence: 70,
    },
    experience: 0,
    dedication: 0,
    officerLevel: 3,
    role: {
        personality: null,
        specialDomestic: null,
        specialWar: null,
        items: {
            horse: null,
            weapon: null,
            book: null,
            item: null,
        },
    },
    injury: 0,
    gold: 1000,
    rice: 1000,
    crew: 1000,
    crewTypeId: 100,
    train: 80,
    atmos: 80,
    age: 20,
    npcState: 0,
    triggerState: {
        flags: {},
        counters: {},
        modifiers: {},
        meta: {},
    },
    meta: {
        killturn: 24,
        dex1: 5000,
    },
});

describe('war triggers', () => {
    it('updates the legacy experience level and applies item experience modifiers immediately', () => {
        const general = buildGeneral(80);
        general.experience = 90;
        general.meta.explevel = 0;
        const crewType = new WarCrewType(buildUnitSet().crewTypes![0]!);
        const unit = new WarUnitGeneral(
            new RandUtil(new ConstantRNG(0)),
            buildConfig(),
            general,
            buildCity(),
            buildNation(),
            true,
            crewType,
            new ActionLogger({ generalId: 1, nationId: 1 }),
            new WarActionPipeline([
                {
                    onCalcStat: (_context, statName, value) =>
                        statName === 'experience' ? (value as number) * 1.2 : value,
                },
            ])
        );

        unit.addLevelExp(10);
        expect(general.experience).toBe(102);
        expect(general.meta.explevel).toBe(1);
    });

    it('applies the legacy 90% dexterity gain for wizard and siege arms', () => {
        const general = buildGeneral(80);
        general.meta.dex5 = 5000;
        const siegeCrew = new WarCrewType({
            ...buildUnitSet().crewTypes![0]!,
            id: 101,
            armType: 5,
            name: '공성병',
        });
        const unit = new WarUnitGeneral(
            new RandUtil(new ConstantRNG(0)),
            buildConfig(),
            general,
            buildCity(),
            buildNation(),
            true,
            siegeCrew,
            new ActionLogger({ generalId: 1, nationId: 1 }),
            new WarActionPipeline([])
        );

        unit.addDex(siegeCrew, 100);
        expect(general.meta.dex5).toBe(5090);
    });

    it('activates and applies critical damage', async () => {
        const rng = new RandUtil(new ConstantRNG(0));
        const config = buildConfig();
        const crewType = new WarCrewType(buildUnitSet().crewTypes![0]!);
        const nation = buildNation();
        const city = buildCity();

        const attacker = new WarUnitGeneral(
            rng,
            config,
            buildGeneral(80),
            city,
            nation,
            true,
            crewType,
            new ActionLogger({ generalId: 1, nationId: 1 }),
            new WarActionPipeline([])
        );
        const defender = new WarUnitCity(
            rng,
            config,
            city,
            nation,
            new WarCrewType(buildUnitSet().crewTypes![1]!),
            new ActionLogger({}),
            200,
            180
        );

        attacker.setOppose(defender);
        defender.setOppose(attacker);

        attacker.beginPhase();

        const [module] = await loadWarTriggerModules(['che_필살']);
        if (!module) {
            throw new Error('Missing che_필살 trigger module');
        }
        const caller = module.createTriggerList(attacker);
        if (!caller) {
            throw new Error('Missing che_필살 trigger list');
        }
        caller.fire({ rng, attacker, defender }, { e_attacker: {}, e_defender: {} });

        expect(attacker.hasActivatedSkill('필살')).toBe(true);
        expect(attacker.getWarPowerMultiply()).toBeCloseTo(1.3);
    });
});

describe('resolveWarBattle', () => {
    it('persists multi-use battle item charges and removes the last charge', async () => {
        const general = buildGeneral(100);
        equipNewItem(general, 'item', 'event_충차', { charges: 2 });
        const itemModules = createItemActionModules(
            createItemModuleRegistry(await loadItemModules(['event_충차']))
        ).war;

        for (const expectedCharges of [1, null] as const) {
            general.crew = 5000;
            general.rice = 10000;
            const defenderCity = { ...buildCity(), wall: 3000, wallMax: 3000 };
            resolveWarBattle({
                rng: new RandUtil(new ConstantRNG(0)),
                unitSet: buildUnitSet(),
                config: buildConfig(),
                time: { year: 200, month: 1, startYear: 180 },
                attacker: {
                    general,
                    city: buildCity(),
                    nation: buildNation(),
                    modules: itemModules,
                },
                defenders: [],
                defenderCity,
                defenderNation: buildNation(),
            });

            const equipped = getEquippedItemInstance(general, 'item');
            if (expectedCharges === null) {
                expect(equipped).toBeNull();
                expect(general.role.items.item).toBeNull();
            } else {
                expect(equipped?.state.charges).toBe(expectedCharges);
                expect(general.role.items.item).toBe('event_충차');
            }
        }
    });

    it('removes a one-use battle item through the canonical inventory', async () => {
        const general = buildGeneral(100);
        equipNewItem(general, 'item', 'che_저격_수극');
        const itemModules = createItemActionModules(
            createItemModuleRegistry(await loadItemModules(['che_저격_수극']))
        ).war;

        resolveWarBattle({
            rng: new RandUtil(new ConstantRNG(0)),
            unitSet: buildUnitSet(),
            config: buildConfig(),
            time: { year: 200, month: 1, startYear: 180 },
            attacker: {
                general,
                city: buildCity(),
                nation: buildNation(),
                modules: itemModules,
            },
            defenders: [],
            defenderCity: buildCity(),
            defenderNation: buildNation(),
        });

        expect(getEquippedItemInstance(general, 'item')).toBeNull();
        expect(general.role.items.item).toBeNull();
    });

    it('handles supply rout when defender nation has no rice', () => {
        const rng = new RandUtil(new ConstantRNG(0));
        const config = buildConfig();
        const unitSet = buildUnitSet();
        const attackerNation = buildNation();
        const defenderNation = { ...buildNation(), rice: 0 };
        const city = buildCity();

        const outcome = resolveWarBattle({
            rng,
            unitSet,
            config,
            time: {
                year: 200,
                month: 1,
                startYear: 180,
            },
            attacker: {
                general: buildGeneral(80),
                city,
                nation: attackerNation,
            },
            defenders: [],
            defenderCity: city,
            defenderNation,
        });

        expect(outcome.conquered).toBe(true);
        expect(outcome.reports.length).toBeGreaterThan(0);
    });
});
