import { TRPCError } from '@trpc/server';
import { z } from 'zod';

import {
    asRecord,
    normalizeArchivedGeneral,
    type ArchivedGeneralSnapshotV1,
    type ArchivedJsonValue,
} from '@sammo-ts/common';

import {
    loadCrewTypeDisplayNames,
    loadItemDisplayNames,
    resolveDedicationLevelName,
    resolveOfficerLevelName,
    sanitizeInternalDisplayCode,
} from '../../services/gameDisplayNames.js';
import {
    findLegacyEmperors,
    findLegacyGeneral,
    findLegacyGeneralBattleResult,
    findLegacyGeneralHallRows,
    findLegacyGeneralsByOwner,
    findLegacyGames,
    findLegacyNations,
    isLegacyArchiveProfile,
    type LegacyArchiveProfile,
    type LegacyGeneralHallRow,
} from '../../services/legacyArchiveStore.js';
import { readOnlyAuthedProcedure, router } from '../../trpc.js';
import { loadTraitNames } from '../nation/shared.js';

const numberOrNull = (value: unknown): number | null =>
    typeof value === 'number' && Number.isFinite(value) ? value : null;

const textOrNull = (value: unknown): string | null => {
    if (typeof value === 'string' && value.trim()) return value;
    if (typeof value === 'number' && Number.isFinite(value)) return String(value);
    return null;
};

const numericText = (value: string | null): number | null => {
    if (value === null || value.trim() === '') return null;
    const parsed = Number(value);
    return Number.isFinite(parsed) ? Math.trunc(parsed) : null;
};

const canonicalSnapshot = (value: unknown, fallbackName: string): ArchivedGeneralSnapshotV1 =>
    normalizeArchivedGeneral(value as ArchivedJsonValue, fallbackName).snapshot;

const zPastPlayDetailInput = z.object({
    source: z.enum(['current', 'legacy']).default('current'),
    serverId: z.string().trim().min(1).max(64),
    generalNo: z.number().int().positive(),
});

type ArchiveSource = 'current' | 'legacy';

interface GeneralArchiveEntry {
    source: ArchiveSource;
    sourceProfile: string;
    serverId: string;
    generalNo: number;
    name: string;
    lastYearMonth: number;
    turnTime: Date;
    snapshot: ArchivedGeneralSnapshotV1;
}

interface ArchiveNationEntry {
    source: ArchiveSource;
    sourceProfile: string;
    serverId: string;
    nation: number;
    archivedAt: Date;
    data: Record<string, unknown>;
}

export interface LegacyHallBattleSummary {
    available: boolean;
    semantics: 'independent-records';
    strategies: number | null;
    warnum: number | null;
    wins: number | null;
    winRate: number | null;
    occupied: number | null;
    killCrew: number | null;
    killRate: number | null;
    killCrewPerson: number | null;
    killRatePerson: number | null;
}

const legacyHallBattleSummary = (rows: LegacyGeneralHallRow[]): LegacyHallBattleSummary => {
    const values = new Map(rows.map((row) => [row.type, row.value]));
    const value = (type: LegacyGeneralHallRow['type']): number | null => values.get(type) ?? null;
    const percent = (type: LegacyGeneralHallRow['type']): number | null => {
        const raw = value(type);
        return raw === null ? null : raw * 100;
    };
    return {
        available: rows.length > 0,
        semantics: 'independent-records',
        strategies: value('firenum'),
        warnum: value('warnum'),
        wins: value('killnum'),
        winRate: percent('winrate'),
        occupied: value('occupied'),
        killCrew: value('killcrew'),
        killRate: percent('killrate'),
        killCrewPerson: value('killcrew_person'),
        killRatePerson: percent('killrate_person'),
    };
};

const key = (source: ArchiveSource, sourceProfile: string, serverId: string): string =>
    `${source}:${sourceProfile}:${serverId}`;

const nationKey = (source: ArchiveSource, sourceProfile: string, serverId: string, nation: number): string =>
    `${key(source, sourceProfile, serverId)}:${nation}`;

const resolveNation = (
    nations: Map<string, ArchiveNationEntry>,
    entry: GeneralArchiveEntry
): { nationId: number; name: string; color: string; level: number | null } => {
    const nationId = entry.snapshot.identity.nationId ?? 0;
    const archived = nations.get(nationKey(entry.source, entry.sourceProfile, entry.serverId, nationId));
    return {
        nationId,
        name: textOrNull(archived?.data.name) ?? (nationId === 0 ? '재야' : '미상'),
        color: textOrNull(archived?.data.color) ?? '#000000',
        level: numberOrNull(archived?.data.level),
    };
};

const resolveDisplayResources = async (entries: GeneralArchiveEntry[]) => {
    const [personalityNames, domesticNames, warNames, itemNames] = await Promise.all([
        loadTraitNames(
            entries.map((entry) => entry.snapshot.traits.personality),
            'personality'
        ),
        loadTraitNames(
            entries.map((entry) => entry.snapshot.traits.specialDomestic),
            'domestic'
        ),
        loadTraitNames(
            entries.map((entry) => entry.snapshot.traits.specialWar),
            'war'
        ),
        loadItemDisplayNames(
            entries.flatMap((entry) => [
                entry.snapshot.items.horse,
                entry.snapshot.items.weapon,
                entry.snapshot.items.book,
                entry.snapshot.items.item,
            ])
        ),
    ]);
    const traitName = (code: string | null, names: Awaited<ReturnType<typeof loadTraitNames>>): string =>
        code ? (names.get(code)?.name ?? sanitizeInternalDisplayCode(code)) : '-';
    const itemName = (code: string | null): string =>
        code ? (itemNames.get(code) ?? sanitizeInternalDisplayCode(code)) : '-';
    return { personalityNames, domesticNames, warNames, traitName, itemName };
};

const buildGeneralDetail = async (entry: GeneralArchiveEntry, nation: ReturnType<typeof resolveNation>) => {
    const snapshot = entry.snapshot;
    const display = await resolveDisplayResources([entry]);
    const crewTypeId = numericText(snapshot.resources.crewType);
    const crewTypeNames = await loadCrewTypeDisplayNames(null, entry.sourceProfile);
    const dedicationLevel = snapshot.progression.dedicationLevel ?? 0;
    const maxDedicationLevel = 30;
    return {
        id: entry.generalNo,
        name: snapshot.identity.name || entry.name,
        picture: snapshot.identity.picture,
        imageServer: snapshot.identity.imageServer,
        npcState: snapshot.identity.npcState ?? 0,
        officerLevel: snapshot.identity.officerLevel ?? 0,
        officerLevelText: resolveOfficerLevelName(snapshot.identity.officerLevel ?? 0, nation.level ?? undefined),
        stats: {
            leadership: snapshot.stats.leadership ?? 0,
            strength: snapshot.stats.strength ?? 0,
            intelligence: snapshot.stats.intelligence ?? 0,
        },
        gold: snapshot.resources.gold ?? 0,
        rice: snapshot.resources.rice ?? 0,
        crew: snapshot.resources.crew ?? 0,
        train: snapshot.resources.train ?? 0,
        atmos: snapshot.resources.morale ?? 0,
        injury: snapshot.resources.injury ?? 0,
        experience: snapshot.progression.experience ?? 0,
        dedication: snapshot.progression.dedication ?? 0,
        ...(snapshot.progression.age === null ? {} : { age: snapshot.progression.age }),
        turnTime: entry.turnTime.toISOString(),
        ...(crewTypeId === null ? {} : { crewTypeId }),
        crewTypeName: crewTypeId === null ? '-' : (crewTypeNames.get(crewTypeId) ?? '-'),
        traits: {
            personal: display.traitName(snapshot.traits.personality, display.personalityNames),
            specialDomestic: display.traitName(snapshot.traits.specialDomestic, display.domesticNames),
            specialWar: display.traitName(snapshot.traits.specialWar, display.warNames),
        },
        itemNames: {
            horse: display.itemName(snapshot.items.horse),
            weapon: display.itemName(snapshot.items.weapon),
            book: display.itemName(snapshot.items.book),
            item: display.itemName(snapshot.items.item),
        },
        progression: {
            experienceLevel: snapshot.progression.experienceLevel ?? 0,
            dedicationLevel,
            dedicationText: resolveDedicationLevelName(dedicationLevel, maxDedicationLevel),
            statExperience: {
                leadership: snapshot.stats.leadershipExperience ?? 0,
                strength: snapshot.stats.strengthExperience ?? 0,
                intelligence: snapshot.stats.intelligenceExperience ?? 0,
            },
            statUpgradeLimit: 30,
            dex: [
                snapshot.mastery.infantry,
                snapshot.mastery.archery,
                snapshot.mastery.cavalry,
                snapshot.mastery.special,
                snapshot.mastery.siege,
            ].map((value) => value ?? 0),
        },
    };
};

export const archiveRouter = router({
    myPastPlays: readOnlyAuthedProcedure.query(async ({ ctx }) => {
        const owner = ctx.auth?.user.id;
        if (!owner) throw new Error('Authenticated archive query is missing its user identity');
        const legacyProfile = isLegacyArchiveProfile(ctx.profile.id) ? ctx.profile.id : null;

        const [legacyRows, currentRows] = await Promise.all([
            legacyProfile ? findLegacyGeneralsByOwner(ctx.db, { owner, sourceProfile: legacyProfile }) : [],
            ctx.db.oldGeneral.findMany({
                where: { owner },
                orderBy: [{ lastYearMonth: 'desc' }, { serverId: 'desc' }, { generalNo: 'asc' }],
            }),
        ]);
        const legacyIdentity = new Set(
            legacyRows.map((row) => `${row.sourceProfile}:${row.serverId}:${row.generalNo}`)
        );
        const entries: GeneralArchiveEntry[] = [
            ...legacyRows.map((row) => ({
                source: 'legacy' as const,
                sourceProfile: row.sourceProfile,
                serverId: row.serverId,
                generalNo: row.generalNo,
                name: row.name,
                lastYearMonth: row.lastYearMonth,
                turnTime: row.turnTime,
                snapshot: canonicalSnapshot(row.data, row.name),
            })),
            ...currentRows
                .filter((row) => !legacyIdentity.has(`${ctx.profile.id}:${row.serverId}:${row.generalNo}`))
                .map((row) => ({
                    source: 'current' as const,
                    sourceProfile: ctx.profile.id,
                    serverId: row.serverId,
                    generalNo: row.generalNo,
                    name: row.name,
                    lastYearMonth: row.lastYearMonth,
                    turnTime: row.turnTime,
                    snapshot: canonicalSnapshot(row.data, row.name),
                })),
        ];
        if (entries.length === 0) return { seasons: [] };

        const legacyKeys = Array.from(
            new Map(
                entries
                    .filter((entry) => entry.source === 'legacy')
                    .map((entry) => [key(entry.source, entry.sourceProfile, entry.serverId), entry])
            ).values()
        ).map((entry) => ({ sourceProfile: entry.sourceProfile as LegacyArchiveProfile, serverId: entry.serverId }));
        const currentServerIds = Array.from(
            new Set(entries.filter((entry) => entry.source === 'current').map((entry) => entry.serverId))
        );
        const [
            legacyGames,
            legacyNationRows,
            legacyEmperors,
            currentGames,
            currentCancellations,
            currentNationRows,
            currentEmperors,
        ] = await Promise.all([
            findLegacyGames(ctx.db, legacyKeys),
            findLegacyNations(ctx.db, legacyKeys),
            findLegacyEmperors(ctx.db, legacyKeys),
            currentServerIds.length
                ? ctx.db.gameHistory.findMany({ where: { serverId: { in: currentServerIds } } })
                : [],
            currentServerIds.length
                ? ctx.db.gameCancellation.findMany({ where: { serverId: { in: currentServerIds } } })
                : [],
            currentServerIds.length
                ? ctx.db.oldNation.findMany({
                      where: { serverId: { in: currentServerIds } },
                      orderBy: [{ date: 'desc' }, { id: 'desc' }],
                  })
                : [],
            currentServerIds.length
                ? ctx.db.emperor.findMany({
                      where: { serverId: { in: currentServerIds } },
                      orderBy: { id: 'desc' },
                      select: { id: true, serverId: true },
                  })
                : [],
        ]);

        const games = new Map<
            string,
            {
                openedAt: Date;
                season: number;
                scenario: number;
                scenarioName: string;
                status?: 'OPEN' | 'COMPLETED' | 'ABANDONED';
                cancellationId?: string;
                cancelledAt?: Date;
            }
        >();
        for (const row of legacyGames) {
            games.set(key('legacy', row.sourceProfile, row.serverId), row);
        }
        for (const row of currentGames) {
            games.set(key('current', ctx.profile.id, row.serverId), {
                openedAt: row.date,
                season: row.season,
                scenario: row.scenario,
                scenarioName: row.scenarioName,
                status: row.status,
            });
        }
        for (const row of currentCancellations) {
            games.set(key('current', ctx.profile.id, row.serverId), {
                openedAt: row.openedAt,
                season: row.originalSeason,
                scenario: row.scenario,
                scenarioName: row.scenarioName,
                status: 'ABANDONED',
                cancellationId: row.id,
                cancelledAt: row.cancelledAt,
            });
        }
        const nations = new Map<string, ArchiveNationEntry>();
        for (const row of legacyNationRows) {
            const entry: ArchiveNationEntry = {
                source: 'legacy',
                sourceProfile: row.sourceProfile,
                serverId: row.serverId,
                nation: row.nation,
                archivedAt: row.archivedAt,
                data: asRecord(row.data),
            };
            const entryKey = nationKey(entry.source, entry.sourceProfile, entry.serverId, entry.nation);
            if (!nations.has(entryKey)) nations.set(entryKey, entry);
        }
        for (const row of currentNationRows) {
            const entry: ArchiveNationEntry = {
                source: 'current',
                sourceProfile: ctx.profile.id,
                serverId: row.serverId,
                nation: row.nation,
                archivedAt: row.date,
                data: asRecord(row.data),
            };
            const entryKey = nationKey(entry.source, entry.sourceProfile, entry.serverId, entry.nation);
            if (!nations.has(entryKey)) nations.set(entryKey, entry);
        }
        const dynastyIds = new Map<string, number>();
        for (const row of legacyEmperors) {
            if (row.serverId) {
                const entryKey = key('legacy', row.sourceProfile, row.serverId);
                if (!dynastyIds.has(entryKey)) dynastyIds.set(entryKey, Number(row.id));
            }
        }
        for (const row of currentEmperors) {
            if (row.serverId) {
                const entryKey = key('current', ctx.profile.id, row.serverId);
                if (!dynastyIds.has(entryKey)) dynastyIds.set(entryKey, row.id);
            }
        }

        const display = await resolveDisplayResources(entries);
        const seasons = new Map<
            string,
            {
                source: ArchiveSource;
                sourceProfile: string;
                serverId: string;
                openedAt: string | null;
                date: string | null;
                season: number | null;
                scenario: number | null;
                scenarioName: string | null;
                status: 'OPEN' | 'COMPLETED' | 'ABANDONED' | 'LEGACY';
                cancellationId: string | null;
                cancelledAt: string | null;
                dynastyId: number | null;
                generals: Array<{
                    generalNo: number;
                    name: string;
                    lastYearMonth: number;
                    nationId: number;
                    nationName: string;
                    nationColor: string;
                    leadership: number | null;
                    strength: number | null;
                    intel: number | null;
                    experience: number | null;
                    dedication: number | null;
                    officerLevel: number | null;
                    officerLevelText: string | null;
                    personal: string | null;
                    special: string | null;
                    special2: string | null;
                    historyCount: number;
                }>;
            }
        >();
        for (const entry of entries) {
            const entryKey = key(entry.source, entry.sourceProfile, entry.serverId);
            const game = games.get(entryKey);
            let season = seasons.get(entryKey);
            if (!season) {
                const openedAt = game?.openedAt.toISOString() ?? null;
                season = {
                    source: entry.source,
                    sourceProfile: entry.sourceProfile,
                    serverId: entry.serverId,
                    openedAt,
                    date: openedAt,
                    season: game?.status === 'ABANDONED' ? null : (game?.season ?? null),
                    scenario: game?.scenario ?? null,
                    scenarioName: game?.scenarioName ?? null,
                    status: entry.source === 'legacy' ? 'LEGACY' : (game?.status ?? 'COMPLETED'),
                    cancellationId: game?.cancellationId ?? null,
                    cancelledAt: game?.cancelledAt?.toISOString() ?? null,
                    dynastyId: game?.status === 'ABANDONED' ? null : (dynastyIds.get(entryKey) ?? null),
                    generals: [],
                };
                seasons.set(entryKey, season);
            }
            const snapshot = entry.snapshot;
            const nation = resolveNation(nations, entry);
            const officerLevel = snapshot.identity.officerLevel;
            season.generals.push({
                generalNo: entry.generalNo,
                name: snapshot.identity.name || entry.name,
                lastYearMonth: entry.lastYearMonth,
                nationId: nation.nationId,
                nationName: nation.name,
                nationColor: nation.color,
                leadership: snapshot.stats.leadership,
                strength: snapshot.stats.strength,
                intel: snapshot.stats.intelligence,
                experience: snapshot.progression.experience,
                dedication: snapshot.progression.dedication,
                officerLevel,
                officerLevelText:
                    officerLevel === null ? null : resolveOfficerLevelName(officerLevel, nation.level ?? undefined),
                personal: display.traitName(snapshot.traits.personality, display.personalityNames),
                special: display.traitName(snapshot.traits.specialDomestic, display.domesticNames),
                special2: display.traitName(snapshot.traits.specialWar, display.warNames),
                historyCount: snapshot.history.length,
            });
        }

        return {
            seasons: [...seasons.values()].sort((left, right) => {
                const leftTime = left.openedAt ? new Date(left.openedAt).getTime() : 0;
                const rightTime = right.openedAt ? new Date(right.openedAt).getTime() : 0;
                return rightTime - leftTime || right.serverId.localeCompare(left.serverId);
            }),
        };
    }),

    myPastPlayDetail: readOnlyAuthedProcedure.input(zPastPlayDetailInput).query(async ({ ctx, input }) => {
        const owner = ctx.auth?.user.id;
        if (!owner) throw new Error('Authenticated archive query is missing its user identity');
        const sourceProfile = ctx.profile.id;

        let entry: GeneralArchiveEntry | null = null;
        let nationRows: ArchiveNationEntry[] = [];
        let dynastyId: number | null = null;
        let battleResultContent: string | null = null;
        let battleResultAvailable = false;
        let hallBattle = legacyHallBattleSummary([]);
        if (input.source === 'legacy') {
            if (!isLegacyArchiveProfile(sourceProfile)) {
                throw new TRPCError({ code: 'NOT_FOUND', message: '지난 장수 기록을 찾을 수 없습니다.' });
            }
            const profile: LegacyArchiveProfile = sourceProfile;
            const row = await findLegacyGeneral(ctx.db, {
                owner,
                sourceProfile: profile,
                serverId: input.serverId,
                generalNo: input.generalNo,
            });
            if (row) {
                entry = {
                    source: 'legacy',
                    sourceProfile: row.sourceProfile,
                    serverId: row.serverId,
                    generalNo: row.generalNo,
                    name: row.name,
                    lastYearMonth: row.lastYearMonth,
                    turnTime: row.turnTime,
                    snapshot: canonicalSnapshot(row.data, row.name),
                };
                const keyInput = [{ sourceProfile: profile, serverId: input.serverId }];
                const [nations, emperors, battleResult, hallRows] = await Promise.all([
                    findLegacyNations(ctx.db, keyInput),
                    findLegacyEmperors(ctx.db, keyInput),
                    findLegacyGeneralBattleResult(ctx.db, {
                        sourceProfile: profile,
                        serverId: input.serverId,
                        generalNo: input.generalNo,
                    }),
                    findLegacyGeneralHallRows(ctx.db, {
                        sourceProfile: profile,
                        serverId: input.serverId,
                        generalNo: input.generalNo,
                    }),
                ]);
                nationRows = nations.map((nation) => ({
                    source: 'legacy',
                    sourceProfile: nation.sourceProfile,
                    serverId: nation.serverId,
                    nation: nation.nation,
                    archivedAt: nation.archivedAt,
                    data: asRecord(nation.data),
                }));
                dynastyId = Number(emperors[0]?.id ?? 0) || null;
                battleResultContent = battleResult?.content ?? null;
                battleResultAvailable = battleResult !== null;
                hallBattle = legacyHallBattleSummary(hallRows);
            }
        } else {
            const row = await ctx.db.oldGeneral.findFirst({
                where: { owner, serverId: input.serverId, generalNo: input.generalNo },
            });
            if (row) {
                entry = {
                    source: 'current',
                    sourceProfile: ctx.profile.id,
                    serverId: row.serverId,
                    generalNo: row.generalNo,
                    name: row.name,
                    lastYearMonth: row.lastYearMonth,
                    turnTime: row.turnTime,
                    snapshot: canonicalSnapshot(row.data, row.name),
                };
                const [nations, emperor] = await Promise.all([
                    ctx.db.oldNation.findMany({
                        where: { serverId: input.serverId },
                        orderBy: [{ date: 'desc' }, { id: 'desc' }],
                    }),
                    ctx.db.emperor.findFirst({ where: { serverId: input.serverId }, orderBy: { id: 'desc' } }),
                ]);
                nationRows = nations.map((nation) => ({
                    source: 'current',
                    sourceProfile: ctx.profile.id,
                    serverId: nation.serverId,
                    nation: nation.nation,
                    archivedAt: nation.date,
                    data: asRecord(nation.data),
                }));
                dynastyId = emperor?.id ?? null;
            }
        }
        if (!entry) {
            throw new TRPCError({ code: 'NOT_FOUND', message: '지난 장수 기록을 찾을 수 없습니다.' });
        }

        const nationMap = new Map<string, ArchiveNationEntry>();
        for (const nation of nationRows) {
            const entryKey = nationKey(nation.source, nation.sourceProfile, nation.serverId, nation.nation);
            if (!nationMap.has(entryKey)) nationMap.set(entryKey, nation);
        }
        const nation = resolveNation(nationMap, entry);
        const snapshot = entry.snapshot;
        const general = await buildGeneralDetail(entry, nation);
        battleResultAvailable ||= snapshot.availability.battleResultLogs;
        const battleResultEntries =
            battleResultContent === null
                ? snapshot.records.battleResult.map((text, index, rows) => ({ id: rows.length - index, text }))
                : battleResultContent
                      .split(/\r?\n/u)
                      .map((text, index) => ({ id: index + 1, text }))
                      .filter((item) => item.text.length > 0)
                      .reverse();
        const logs = {
            generalHistory: {
                available: snapshot.availability.history,
                entries: snapshot.history.map((text, index) => ({ id: index + 1, text })),
            },
            battleDetail: { available: snapshot.availability.battleDetailLogs, entries: [] },
            battleResult: { available: battleResultAvailable, entries: battleResultEntries },
            generalAction: { available: false, entries: [] },
        };
        return {
            source: entry.source,
            sourceProfile: entry.sourceProfile,
            serverId: entry.serverId,
            generalNo: entry.generalNo,
            sourceFormat: input.source === 'legacy' ? 'normalized-v1' : 'current-archive',
            dynastyPath:
                dynastyId === null ? null : `/dynasty/${dynastyId}${entry.source === 'legacy' ? '?source=legacy' : ''}`,
            nation: { id: nation.nationId, name: nation.name, color: nation.color },
            general,
            masteryAvailable: snapshot.availability.mastery,
            battle: {
                available: snapshot.availability.battleAggregates,
                warnum: snapshot.battle.battles,
                wins: snapshot.battle.wins,
                losses: snapshot.battle.losses,
                strategies: snapshot.battle.fireSuccesses,
                killCrew: snapshot.battle.killedCrew,
                deathCrew: snapshot.battle.lostCrew,
                winRate: snapshot.battle.winRate,
                killRate: snapshot.battle.killRate,
                recentWar: snapshot.battle.recentWar,
            },
            hallBattle,
            logs,
        };
    }),
});
