import { afterAll, beforeAll, describe, expect, it } from 'vitest';
import { asRecord } from '@sammo-ts/common';
import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';
import { LogCategory, LogScope } from '@sammo-ts/logic';

import type { GeneralLifecycleEvent } from '../src/turn/inMemoryWorld.js';
import { persistGeneralLifecycleEvents } from '../src/turn/generalTurnLifecyclePersistence.js';
import type { TurnGeneral } from '../src/turn/types.js';

const databaseUrl = process.env.GENERAL_LIFECYCLE_DATABASE_URL;
const integration = describe.skipIf(!databaseUrl);
const generalIds = [990_001, 990_002, 990_003];
const userIds = ['integration-lifecycle-dead', 'integration-lifecycle-retired', 'integration-lifecycle-possessed'];
const serverId = 'lifecycle-int';

const makeGeneral = (id: number, userId: string, patch: Partial<TurnGeneral> = {}): TurnGeneral => ({
    id,
    userId,
    name: `lifecycle-${id}`,
    nationId: 0,
    cityId: 0,
    troopId: 0,
    stats: { leadership: 80, strength: 70, intelligence: 60 },
    experience: 1_000,
    dedication: 800,
    officerLevel: 1,
    role: {
        personality: null,
        specialDomestic: null,
        specialWar: null,
        items: { horse: null, weapon: null, book: null, item: null },
    },
    injury: 0,
    gold: 1_000,
    rice: 1_000,
    crew: 0,
    crewTypeId: 1,
    train: 0,
    atmos: 0,
    age: 80,
    npcState: 0,
    bornYear: 170,
    deadYear: 260,
    affinity: 50,
    triggerState: { flags: {}, counters: {}, modifiers: {}, meta: {} },
    meta: {
        killturn: 0,
        inherit_lived_month: 10,
        inherit_active_action: 2,
        dex1: 1_000,
    },
    turnTime: new Date('0200-01-01T00:00:00.000Z'),
    ...patch,
});

const event = (general: TurnGeneral, outcome: GeneralLifecycleEvent['outcome']): GeneralLifecycleEvent => ({
    generalId: general.id,
    outcome,
    before: general,
    ...(outcome === 'deleted' ? {} : { after: general }),
    year: 200,
    month: 1,
});

integration('general turn lifecycle persistence', () => {
    let db: GamePrismaClient;
    let close: (() => Promise<void>) | undefined;

    const cleanup = async () => {
        await db.logEntry.deleteMany({ where: { generalId: { in: generalIds } } });
        await db.generalAccessLog.deleteMany({ where: { generalId: { in: generalIds } } });
        await db.rankData.deleteMany({ where: { generalId: { in: generalIds } } });
        await db.oldGeneral.deleteMany({ where: { serverId, generalNo: { in: generalIds } } });
        await db.hallOfFame.deleteMany({ where: { serverId, generalNo: { in: generalIds } } });
        await db.inheritanceResult.deleteMany({ where: { serverId, owner: { in: userIds } } });
        await db.inheritanceLog.deleteMany({ where: { userId: { in: userIds } } });
        await db.inheritancePoint.deleteMany({ where: { userId: { in: userIds } } });
    };

    beforeAll(async () => {
        const connector = createGamePostgresConnector({ url: databaseUrl! });
        await connector.connect();
        db = connector.prisma;
        close = () => connector.disconnect();
        await cleanup();
    });

    afterAll(async () => {
        await cleanup();
        await close?.();
    });

    it('archives deletion, removes access state, and settles inheritance', async () => {
        const general = makeGeneral(generalIds[0]!, userIds[0]!, {
            meta: {
                killturn: 0,
                inherit_lived_month: 10,
                inherit_active_action: 2,
                inheritRandomUnique: true,
                dex1: 1_000,
            },
        });
        await db.generalAccessLog.create({
            data: { generalId: general.id, userId: general.userId, refreshScore: 99 },
        });
        await db.inheritancePoint.create({
            data: { userId: general.userId!, key: 'previous', value: 100 },
        });
        await db.rankData.createMany({
            data: [
                { generalId: general.id, nationId: 0, type: 'warnum', value: 2 },
                { generalId: general.id, nationId: 0, type: 'firenum', value: 1 },
            ],
        });
        await db.logEntry.createMany({
            data: [
                {
                    scope: LogScope.GENERAL,
                    category: LogCategory.HISTORY,
                    year: 199,
                    month: 12,
                    generalId: general.id,
                    text: '<C>●</>첫 기록',
                },
                {
                    scope: LogScope.GENERAL,
                    category: LogCategory.HISTORY,
                    year: 200,
                    month: 1,
                    generalId: general.id,
                    text: '<Y>●</>둘째 기록',
                },
            ],
        });

        await db.$transaction((tx) =>
            persistGeneralLifecycleEvents(
                tx,
                [event(general, 'deleted')],
                { serverId },
                { inheritItemRandomPoint: 3_000, inheritSpecificSpecialPoint: 4_000 }
            )
        );

        expect(await db.generalAccessLog.findUnique({ where: { generalId: general.id } })).toBeNull();
        const archived = await db.oldGeneral.findUniqueOrThrow({
            where: { by_no: { serverId, generalNo: general.id } },
        });
        const archivedData = asRecord(archived.data);
        expect(archivedData.history).toEqual(['<Y>●</>둘째 기록', '<C>●</>첫 기록']);
        expect(asRecord(archivedData.meta)).not.toHaveProperty('inheritRandomUnique');
        expect(asRecord(archivedData.meta)).not.toHaveProperty('inheritSpecificSpecialWar');
        expect(
            await db.inheritancePoint.findUnique({
                where: { userId_key: { userId: general.userId!, key: 'previous' } },
            })
        ).toMatchObject({ value: 3_147 });
        expect(
            (
                await db.inheritanceLog.findMany({
                    where: { userId: general.userId! },
                    orderBy: { id: 'asc' },
                    select: { text: true },
                })
            ).map(({ text }) => text)
        ).toEqual(['사망으로 랜덤 유니크 구입 3000 포인트 반환', '사망 정산: 3,147 포인트']);
    });

    it('resets access/ranks and records pre-rebirth hall and inheritance values', async () => {
        const general = makeGeneral(generalIds[1]!, userIds[1]!);
        await db.generalAccessLog.create({
            data: { generalId: general.id, userId: general.userId, refreshScore: 77 },
        });
        await db.inheritancePoint.create({
            data: { userId: general.userId!, key: 'previous', value: 50 },
        });
        await db.rankData.create({
            data: { generalId: general.id, nationId: 0, type: 'warnum', value: 10 },
        });

        await db.$transaction((tx) =>
            persistGeneralLifecycleEvents(
                tx,
                [event(general, 'retired')],
                { serverId, season: 1, scenarioId: 2, isUnited: 0 },
                {}
            )
        );

        expect(await db.generalAccessLog.findUnique({ where: { generalId: general.id } })).toMatchObject({
            refreshScore: 0,
        });
        expect(
            await db.rankData.findUnique({ where: { generalId_type: { generalId: general.id, type: 'warnum' } } })
        ).toMatchObject({ value: 0 });
        expect(
            await db.hallOfFame.findUnique({
                where: {
                    serverId_type_generalNo: {
                        serverId,
                        type: 'experience',
                        generalNo: general.id,
                    },
                },
            })
        ).toMatchObject({ value: 1_000 });
        expect(
            await db.inheritancePoint.findUnique({
                where: { userId_key: { userId: general.userId!, key: 'previous' } },
            })
        ).toMatchObject({ value: 116 });
    });

    it('does not settle a possessed NPC before the legacy minimum possession period', async () => {
        const general = makeGeneral(generalIds[2]!, userIds[2]!, {
            npcState: 1,
            meta: {
                killturn: 0,
                inherit_lived_month: 10,
                inherit_active_action: 2,
                pickYearMonth: 190 * 12,
            },
        });
        await db.inheritancePoint.create({
            data: { userId: general.userId!, key: 'previous', value: 100 },
        });

        await db.$transaction((tx) =>
            persistGeneralLifecycleEvents(tx, [event(general, 'deleted')], { serverId, startYear: 180 }, {})
        );

        expect(
            await db.inheritancePoint.findUnique({
                where: { userId_key: { userId: general.userId!, key: 'previous' } },
            })
        ).toMatchObject({ value: 100 });
        expect(
            await db.inheritanceResult.count({
                where: { serverId, owner: general.userId! },
            })
        ).toBe(0);
    });
});
