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

import type { GamePrisma } from '@sammo-ts/infra';
import type { MessagePayload } from '@sammo-ts/logic';

import { respondToActionableMessage } from '../src/turn/actionableMessageResponse.js';
import { InMemoryTurnWorld } from '../src/turn/inMemoryWorld.js';
import type { ImmediateGeneralActionExecutor } from '../src/turn/reservedTurnHandler.js';
import type { TurnGeneral, TurnWorldSnapshot, TurnWorldState } from '../src/turn/types.js';

const actor: TurnGeneral = {
    id: 7,
    userId: 'user-7',
    name: '수신자',
    nationId: 1,
    cityId: 1,
    troopId: 0,
    stats: { leadership: 70, strength: 60, intelligence: 50 },
    turnTime: new Date('0200-01-01T00:10:00.000Z'),
    role: {
        items: { horse: null, weapon: null, book: null, item: null },
        personality: null,
        specialDomestic: null,
        specialWar: null,
    },
    triggerState: { flags: {}, counters: {}, modifiers: {}, meta: {} },
    meta: { killturn: 24 },
    penalty: {},
    officerLevel: 1,
    experience: 100,
    dedication: 100,
    injury: 0,
    gold: 1_000,
    rice: 1_000,
    crew: 0,
    crewTypeId: 0,
    train: 0,
    atmos: 0,
    age: 30,
    npcState: 0,
};

const buildWorld = (): InMemoryTurnWorld => {
    const state: TurnWorldState = {
        id: 1,
        currentYear: 200,
        currentMonth: 1,
        tickSeconds: 600,
        lastTurnTime: new Date('0200-01-01T00:00:00.000Z'),
        meta: { hiddenSeed: 'actionable-message-test', isunited: 2 },
    };
    const snapshot: TurnWorldSnapshot = {
        generals: [actor],
        cities: [],
        nations: [],
        troops: [],
        diplomacy: [],
        events: [],
        initialEvents: [],
        scenarioConfig: {
            stat: { total: 300, min: 10, max: 100, npcTotal: 150, npcMax: 50, npcMin: 10, chiefMin: 70 },
            iconPath: '',
            map: {},
            const: {},
            environment: { mapName: 'test', unitSet: 'default' },
        },
        map: { id: 'test', name: 'test', cities: [] },
    };
    return new InMemoryTurnWorld(state, snapshot, {
        schedule: { entries: [{ startMinute: 0, tickMinutes: 10 }] },
    });
};

const source = {
    generalId: 8,
    generalName: '제안자',
    nationId: 2,
    nationName: '촉',
    color: '#000000',
    icon: '',
};
const destination = {
    generalId: actor.id,
    generalName: actor.name,
    nationId: actor.nationId,
    nationName: '위',
    color: '#ffffff',
    icon: '',
};

const buildRow = (action: 'scout' | 'raiseInvader', overrides: Partial<MessagePayload> = {}) => ({
    id: 29,
    mailbox: actor.id,
    type: 'private',
    validUntil: new Date('9999-12-31T00:00:00.000Z'),
    message: {
        src: source,
        dest: destination,
        text: '응답할 메시지',
        option: { action, used: false, ...(action === 'raiseInvader' ? { args: [-2, -1.2, -1, -0.5] } : {}) },
        ...overrides,
    } satisfies MessagePayload,
});

const buildDb = (rows: unknown[][]) => {
    const queryRaw = vi.fn(async () => rows.shift() ?? []);
    const updateMany = vi.fn(async () => ({ count: 1 }));
    return {
        db: { $queryRaw: queryRaw, message: { updateMany } } as unknown as GamePrisma.TransactionClient,
        queryRaw,
        updateMany,
    };
};

const buildExecutor = (ok = true): ImmediateGeneralActionExecutor => ({
    execute: vi.fn(async () => (ok ? { ok: true } : { ok: false, reason: '등용 수락 불가.' })),
});

describe('actionable message response', () => {
    it('accepts a recruitment letter, executes the legacy action, and invalidates linked prompts', async () => {
        const world = buildWorld();
        const row = buildRow('scout');
        const { db, updateMany } = buildDb([[row], [{ id: 31 }]]);
        const executor = buildExecutor();

        const result = await respondToActionableMessage({
            db,
            world,
            executor,
            userId: actor.userId!,
            generalId: actor.id,
            messageId: row.id,
            response: true,
        });

        expect(result).toEqual({ ok: true, action: 'scout', reason: 'success' });
        expect(executor.execute).toHaveBeenCalledWith(
            expect.objectContaining({
                actionKey: 'che_등용수락',
                generalId: actor.id,
                args: { destNationId: source.nationId, destGeneralId: source.generalId },
            })
        );
        expect(updateMany).toHaveBeenCalledWith(expect.objectContaining({ where: { id: { in: [row.id, 31] } } }));
        expect(world.peekDirtyState().messages).toEqual([
            expect.objectContaining({
                msgType: 'private',
                text: '촉으로 등용 제의 수락',
                sendDestOnly: true,
                option: { delete: row.id },
            }),
        ]);
    });

    it('keeps a recruitment letter valid when the legacy accept constraints reject it', async () => {
        const world = buildWorld();
        const row = buildRow('scout');
        const { db, updateMany } = buildDb([[row]]);

        const result = await respondToActionableMessage({
            db,
            world,
            executor: buildExecutor(false),
            userId: actor.userId!,
            generalId: actor.id,
            messageId: row.id,
            response: true,
        });

        expect(result).toEqual({ ok: true, action: 'scout', reason: '등용 수락 불가.' });
        expect(updateMany).not.toHaveBeenCalled();
        expect(world.peekDirtyState().messages).toHaveLength(0);
    });

    it('does not invalidate an invader prompt before validating its receiver', async () => {
        const world = buildWorld();
        const row = { ...buildRow('raiseInvader'), mailbox: 99 };
        const { db, updateMany } = buildDb([[row]]);

        const result = await respondToActionableMessage({
            db,
            world,
            executor: buildExecutor(),
            userId: actor.userId!,
            generalId: actor.id,
            messageId: row.id,
            response: false,
        });

        expect(result).toEqual({ ok: false, action: 'raiseInvader', reason: '올바른 수신자가 아닙니다.' });
        expect(updateMany).not.toHaveBeenCalled();
    });

    it('invalidates a valid invader prompt when it is declined', async () => {
        const world = buildWorld();
        const row = buildRow('raiseInvader');
        const { db, updateMany } = buildDb([[row]]);

        const result = await respondToActionableMessage({
            db,
            world,
            executor: buildExecutor(),
            userId: actor.userId!,
            generalId: actor.id,
            messageId: row.id,
            response: false,
        });

        expect(result).toEqual({ ok: true, action: 'raiseInvader', reason: 'success' });
        expect(updateMany).toHaveBeenCalledOnce();
    });
});
