import { afterAll, beforeAll, beforeEach, describe, expect, it } from 'vitest';

import { createGamePostgresConnector, type GamePrismaClient } from '@sammo-ts/infra';

import { cancelGame } from '../src/scenario/gameCancellation.js';

const databaseUrl = process.env.INPUT_EVENT_DATABASE_URL;
const integration = describe.skipIf(!databaseUrl);
const serverId = 'che_game_cancellation_fixture';
const userId = 'game-cancellation-user';
const generalId = 9_851;
const openedAt = new Date('2026-08-18T00:00:00.000Z');
const cancelledAt = new Date('2026-08-18T01:00:00.000Z');

integration('game cancellation transaction', () => {
    let db: GamePrismaClient;
    let closeDb: (() => Promise<void>) | undefined;

    const cleanup = async (): Promise<void> => {
        await db.gameCancellation.deleteMany({ where: { serverId } });
        await db.gameInheritanceBaseline.deleteMany({ where: { serverId } });
        await db.unificationFinalization.deleteMany({ where: { serverId } });
        await db.yearbookHistory.deleteMany({ where: { profileName: serverId } });
        await db.emperor.deleteMany({ where: { serverId } });
        await db.oldGeneral.deleteMany({ where: { serverId } });
        await db.oldNation.deleteMany({ where: { serverId } });
        await db.hallOfFame.deleteMany({ where: { serverId } });
        await db.inheritanceResult.deleteMany({ where: { serverId } });
        await db.inheritanceLog.deleteMany({ where: { userId } });
        await db.inheritancePoint.deleteMany({ where: { userId } });
        await db.gameHistory.deleteMany({ where: { serverId } });
        await db.logEntry.deleteMany({ where: { generalId } });
        await db.rankData.deleteMany({ where: { generalId } });
        await db.general.deleteMany({ where: { id: generalId } });
        await db.worldState.deleteMany({ where: { scenarioCode: 'game-cancellation-fixture' } });
    };

    const seed = async (): Promise<void> => {
        await db.worldState.create({
            data: {
                scenarioCode: 'game-cancellation-fixture',
                currentYear: 190,
                currentMonth: 7,
                tickSeconds: 600,
                meta: { serverId, season: 7, isUnited: 0 },
            },
        });
        await db.gameHistory.create({
            data: {
                serverId,
                date: openedAt,
                season: 7,
                scenario: 1010,
                scenarioName: '취소 테스트',
                status: 'OPEN',
                env: { meta: { serverId, season: 7 } },
            },
        });
        await db.general.create({
            data: {
                id: generalId,
                userId,
                name: '취소장수',
                turnTime: openedAt,
                meta: { inherit_spent_dyn: 4_500 },
            },
        });
        await db.rankData.createMany({
            data: [
                { generalId, nationId: 0, type: 'inherit_spent_dyn', value: 4_500 },
                { generalId, nationId: 0, type: 'warnum', value: 10 },
            ],
        });
        await db.inheritancePoint.createMany({
            data: [
                { userId, key: 'previous', value: 7_000 },
                { userId, key: 'max_domestic_critical', value: 200 },
            ],
        });
        await db.gameInheritanceBaseline.create({
            data: { serverId, userId, openingPoint: 10_000, source: 'OPENING' },
        });
        await db.inheritanceLog.create({
            data: {
                userId,
                serverId,
                year: 190,
                month: 7,
                text: '신규/복귀 생성으로 포인트 1500 지급',
                createdAt: new Date('2026-08-18T00:30:00.000Z'),
            },
        });
        await db.oldGeneral.create({
            data: {
                serverId,
                generalNo: generalId - 1,
                owner: userId,
                name: '사망장수',
                lastYearMonth: 19006,
                turnTime: openedAt,
                data: { meta: { inherit_spent_dyn: 0 } },
            },
        });
        await db.hallOfFame.create({
            data: { serverId, season: 7, scenario: 1010, generalNo: generalId, type: 'warnum', value: 10 },
        });
        await db.oldNation.create({ data: { serverId, nation: 1, sourceId: 1 } });
        await db.emperor.create({ data: { serverId, name: '취소 황제' } });
        await db.yearbookHistory.create({
            data: { profileName: serverId, year: 190, month: 7, map: {}, nations: {} },
        });
        await db.unificationFinalization.create({
            data: {
                generationKey: `${serverId}:fixture`,
                serverId,
                profileName: 'che',
                winnerNation: 1,
                year: 190,
                month: 7,
                completedAt: cancelledAt,
            },
        });
        await db.inheritanceResult.create({
            data: {
                serverId,
                owner: userId,
                generalId,
                year: 190,
                month: 7,
                value: { refund: 0 },
            },
        });
    };

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

    beforeEach(async () => {
        await cleanup();
        await seed();
    });

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

    it('rolls back a late failure, then refunds spending and retains selected earnings exactly once', async () => {
        const request = {
            cancellationId: 'game-cancellation-retain-fixture',
            databaseUrl: databaseUrl!,
            cancelledBy: 'admin',
            reason: '잘못 연 게임 취소',
            historyMode: 'RETAIN_ABANDONED' as const,
            generalMode: 'RETAIN' as const,
            earnedPointRetentionPercent: 40,
            cancelledAt,
        };

        await expect(cancelGame({ ...request, cancelledBy: 'admin\0invalid' })).rejects.toThrow();
        await expect(db.gameHistory.findUniqueOrThrow({ where: { serverId } })).resolves.toMatchObject({
            status: 'OPEN',
        });
        await expect(
            db.inheritancePoint.findUniqueOrThrow({ where: { userId_key: { userId, key: 'previous' } } })
        ).resolves.toMatchObject({ value: 7_000 });
        await expect(db.hallOfFame.count({ where: { serverId } })).resolves.toBe(1);
        await expect(db.gameCancellation.count({ where: { serverId } })).resolves.toBe(0);

        const result = await cancelGame(request);
        expect(result).toMatchObject({
            participantCount: 1,
            preservedGeneralCount: 2,
            alreadyApplied: false,
            settlements: {
                [userId]: {
                    openingPoint: 10_000,
                    currentPoint: 7_000,
                    earnedPoint: 1_750,
                    retainedEarnedPoint: 700,
                    finalPoint: 10_700,
                    baselineSource: 'OPENING',
                },
            },
        });
        await expect(
            db.inheritancePoint.findMany({ where: { userId }, orderBy: { key: 'asc' } })
        ).resolves.toMatchObject([{ key: 'previous', value: 10_700 }]);
        await expect(db.gameHistory.findUniqueOrThrow({ where: { serverId } })).resolves.toMatchObject({
            status: 'ABANDONED',
            winnerNation: null,
        });
        const archived = await db.oldGeneral.findMany({ where: { serverId }, orderBy: { generalNo: 'asc' } });
        expect(archived).toHaveLength(2);
        expect(archived.every((row) => JSON.stringify(row.data).includes(request.cancellationId))).toBe(true);
        await expect(db.hallOfFame.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.oldNation.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.emperor.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.yearbookHistory.count({ where: { profileName: serverId } })).resolves.toBe(0);
        await expect(db.unificationFinalization.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.inheritanceResult.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.worldState.findFirstOrThrow()).resolves.toMatchObject({
            meta: expect.objectContaining({ isCancelled: 1, cancellationId: request.cancellationId }),
        });

        await expect(cancelGame(request)).resolves.toMatchObject({ alreadyApplied: true });
        await expect(
            db.inheritanceLog.count({ where: { userId, text: { startsWith: '취소 게임 정산:' } } })
        ).resolves.toBe(1);
    });

    it('physically deletes the numbered history row and past-play general archive on request', async () => {
        const result = await cancelGame({
            cancellationId: 'game-cancellation-delete-fixture',
            databaseUrl: databaseUrl!,
            cancelledBy: 'admin',
            reason: '기수와 장수 기록 삭제',
            historyMode: 'DELETE',
            generalMode: 'DELETE',
            earnedPointRetentionPercent: 0,
            cancelledAt,
        });

        expect(result).toMatchObject({ participantCount: 1, preservedGeneralCount: 0, alreadyApplied: false });
        await expect(db.gameHistory.findUnique({ where: { serverId } })).resolves.toBeNull();
        await expect(db.oldGeneral.count({ where: { serverId } })).resolves.toBe(0);
        await expect(db.gameCancellation.findUnique({ where: { serverId } })).resolves.toMatchObject({
            originalSeason: 7,
            historyMode: 'DELETE',
            generalMode: 'DELETE',
        });
    });
});
