<?php

declare(strict_types=1);

namespace sammo;

if (PHP_SAPI !== 'cli') {
    http_response_code(404);
    exit;
}

chdir(dirname(__DIR__));
require_once 'lib.php';
require_once 'func.php';

/** @return list<int> */
function comparisonIntegerList(mixed $value, string $label): array
{
    if ($value === null) {
        return [];
    }
    if (!is_array($value)) {
        throw new \InvalidArgumentException("{$label} must be an array");
    }
    $result = [];
    foreach ($value as $entry) {
        if (!is_int($entry) || $entry < 0) {
            throw new \InvalidArgumentException("{$label} entries must be non-negative integers");
        }
        $result[$entry] = $entry;
    }
    ksort($result, SORT_NUMERIC);
    return array_values($result);
}

function comparisonJsonValue(mixed $value): mixed
{
    if (!is_string($value) || $value === '') {
        return $value;
    }
    try {
        return json_decode($value, true, flags: JSON_THROW_ON_ERROR);
    } catch (\JsonException) {
        return $value;
    }
}

function comparisonOptionalCode(mixed $value): ?string
{
    if (!is_string($value) || $value === '' || $value === 'None') {
        return null;
    }
    return $value;
}

/** @param array<string, mixed> $row */
function comparisonPickRow(array $row, array $mapping, array $jsonKeys = []): array
{
    $result = [];
    foreach ($mapping as $canonical => $legacy) {
        if (!array_key_exists($legacy, $row)) {
            continue;
        }
        $value = $row[$legacy];
        if (in_array($legacy, $jsonKeys, true)) {
            $value = comparisonJsonValue($value);
        }
        $result[$canonical] = $value;
    }
    return $result;
}

/** @param list<int> $ids */
function comparisonRowsById(string $table, string $idColumn, array $ids): array
{
    $db = DB::db();
    $rows = [];
    foreach ($ids as $id) {
        $row = $db->queryFirstRow(
            "SELECT * FROM `{$table}` WHERE `{$idColumn}` = %i",
            $id,
        );
        if ($row !== null) {
            $rows[] = $row;
        }
    }
    return $rows;
}

function comparisonTurnStateSnapshot(array $request): array
{
    $observe = $request['observe'] ?? [];
    if (!is_array($observe)) {
        throw new \InvalidArgumentException('observe must be an object');
    }
    $generalIds = comparisonIntegerList($observe['generalIds'] ?? [], 'generalIds');
    $cityIds = comparisonIntegerList($observe['cityIds'] ?? [], 'cityIds');
    $nationIds = comparisonIntegerList($observe['nationIds'] ?? [], 'nationIds');
    $logAfterId = $observe['logAfterId'] ?? 0;
    $messageAfterId = $observe['messageAfterId'] ?? 0;
    if (!is_int($logAfterId) || $logAfterId < 0 || !is_int($messageAfterId) || $messageAfterId < 0) {
        throw new \InvalidArgumentException('logAfterId and messageAfterId must be non-negative integers');
    }

    $db = DB::db();
    $game = KVStorage::getStorage($db, 'game_env');
    $game->resetCache();
    $worldValues = $game->getValues([
        'year',
        'month',
        'turnterm',
        'turntime',
        'isunited',
        'scenario',
        'init_year',
        'init_month',
        'develcost',
    ]);

    $generals = array_map(
        static function (array $row): array {
            $result = comparisonPickRow(
                $row,
            [
                'id' => 'no',
                'name' => 'name',
                'nationId' => 'nation',
                'cityId' => 'city',
                'troopId' => 'troop',
                'leadership' => 'leadership',
                'strength' => 'strength',
                'intelligence' => 'intel',
                'experience' => 'experience',
                'dedication' => 'dedication',
                'expLevel' => 'explevel',
                'officerLevel' => 'officer_level',
                'injury' => 'injury',
                'gold' => 'gold',
                'rice' => 'rice',
                'crew' => 'crew',
                'crewTypeId' => 'crewtype',
                'train' => 'train',
                'atmos' => 'atmos',
                'age' => 'age',
                'npcState' => 'npc',
                'turnTime' => 'turntime',
                'recentWarTime' => 'recent_war',
                'lastTurn' => 'last_turn',
                'meta' => 'aux',
                'leadershipExp' => 'leadership_exp',
                'strengthExp' => 'strength_exp',
                'intelExp' => 'intel_exp',
                'dex1' => 'dex1',
                'dex2' => 'dex2',
                'dex3' => 'dex3',
                'dex4' => 'dex4',
                'dex5' => 'dex5',
                'killTurn' => 'killturn',
                'mySet' => 'myset',
                'specialDomestic' => 'special',
                'specialWar' => 'special2',
                'personality' => 'personal',
                'itemHorse' => 'horse',
                'itemWeapon' => 'weapon',
                'itemBook' => 'book',
                'itemExtra' => 'item',
            ],
            ['last_turn', 'aux'],
            );
            foreach ([
                'specialDomestic',
                'specialWar',
                'personality',
                'itemHorse',
                'itemWeapon',
                'itemBook',
                'itemExtra',
            ] as $key) {
                $result[$key] = comparisonOptionalCode($result[$key] ?? null);
            }
            return $result;
        },
        comparisonRowsById('general', 'no', $generalIds),
    );

    $cities = array_map(
        static fn(array $row): array => comparisonPickRow(
            $row,
            [
                'id' => 'city',
                'name' => 'name',
                'nationId' => 'nation',
                'level' => 'level',
                'population' => 'pop',
                'populationMax' => 'pop_max',
                'agriculture' => 'agri',
                'agricultureMax' => 'agri_max',
                'commerce' => 'comm',
                'commerceMax' => 'comm_max',
                'security' => 'secu',
                'securityMax' => 'secu_max',
                'supplyState' => 'supply',
                'frontState' => 'front',
                'defence' => 'def',
                'defenceMax' => 'def_max',
                'wall' => 'wall',
                'wallMax' => 'wall_max',
                'state' => 'state',
                'term' => 'term',
                'trust' => 'trust',
                'trade' => 'trade',
            ],
        ),
        comparisonRowsById('city', 'city', $cityIds),
    );

    $nations = array_map(
        static fn(array $row): array => comparisonPickRow(
            $row,
            [
                'id' => 'nation',
                'name' => 'name',
                'color' => 'color',
                'capitalCityId' => 'capital',
                'gold' => 'gold',
                'rice' => 'rice',
                'tech' => 'tech',
                'level' => 'level',
                'typeCode' => 'type',
                'generalCount' => 'gennum',
                'power' => 'power',
                'war' => 'war',
                'diplomacyLimit' => 'surlimit',
                'meta' => 'aux',
            ],
            ['aux'],
        ),
        comparisonRowsById('nation', 'nation', $nationIds),
    );

    $diplomacy = [];
    foreach ($nationIds as $fromNationId) {
        foreach ($nationIds as $toNationId) {
            if ($fromNationId === $toNationId) {
                continue;
            }
            $row = $db->queryFirstRow(
                'SELECT me, you, state, term, dead FROM diplomacy WHERE me = %i AND you = %i',
                $fromNationId,
                $toNationId,
            );
            if ($row !== null) {
                $diplomacy[] = comparisonPickRow($row, [
                    'fromNationId' => 'me',
                    'toNationId' => 'you',
                    'state' => 'state',
                    'term' => 'term',
                    'dead' => 'dead',
                ]);
            }
        }
    }

    $generalTurns = [];
    foreach ($generalIds as $generalId) {
        foreach ($db->query(
            'SELECT general_id, turn_idx, action, arg FROM general_turn WHERE general_id = %i ORDER BY turn_idx',
            $generalId,
        ) as $row) {
            $generalTurns[] = comparisonPickRow(
                $row,
                ['generalId' => 'general_id', 'turnIndex' => 'turn_idx', 'action' => 'action', 'args' => 'arg'],
                ['arg'],
            );
        }
    }

    $nationTurns = [];
    foreach ($nationIds as $nationId) {
        foreach ($db->query(
            'SELECT nation_id, officer_level, turn_idx, action, arg FROM nation_turn WHERE nation_id = %i ORDER BY officer_level, turn_idx',
            $nationId,
        ) as $row) {
            $nationTurns[] = comparisonPickRow(
                $row,
                [
                    'nationId' => 'nation_id',
                    'officerLevel' => 'officer_level',
                    'turnIndex' => 'turn_idx',
                    'action' => 'action',
                    'args' => 'arg',
                ],
                ['arg'],
            );
        }
    }

    $logs = [];
    if ($generalIds !== []) {
        foreach ($db->query(
            'SELECT id, general_id, log_type, year, month, text FROM general_record WHERE id > %i ORDER BY id',
            $logAfterId,
        ) as $row) {
            if ((int)$row['general_id'] !== 0 && !in_array((int)$row['general_id'], $generalIds, true)) {
                continue;
            }
            $logs[] = comparisonPickRow($row, [
                'id' => 'id',
                'generalId' => 'general_id',
                'category' => 'log_type',
                'year' => 'year',
                'month' => 'month',
                'text' => 'text',
            ]);
        }
    }

    $messages = array_map(
        static fn(array $row): array => comparisonPickRow(
            $row,
            [
                'id' => 'id',
                'mailbox' => 'mailbox',
                'type' => 'type',
                'sourceId' => 'src',
                'destinationId' => 'dest',
                'createdAt' => 'time',
                'payload' => 'message',
            ],
            ['message'],
        ),
        $db->query(
            'SELECT id, mailbox, type, src, dest, time, message FROM message WHERE id > %i ORDER BY id',
            $messageAfterId,
        ),
    );

    return [
        'schemaVersion' => 1,
        'engine' => 'ref',
        'world' => [
            'year' => (int)$worldValues['year'],
            'month' => (int)$worldValues['month'],
            'tickMinutes' => (int)$worldValues['turnterm'],
            'turnTime' => (string)$worldValues['turntime'],
            'isUnited' => (int)$worldValues['isunited'],
            'scenarioId' => (int)$worldValues['scenario'],
            'initYear' => (int)$worldValues['init_year'],
            'initMonth' => (int)$worldValues['init_month'],
            'develCost' => (int)$worldValues['develcost'],
        ],
        'generals' => $generals,
        'cities' => $cities,
        'nations' => $nations,
        'diplomacy' => $diplomacy,
        'generalTurns' => $generalTurns,
        'nationTurns' => $nationTurns,
        'logs' => $logs,
        'messages' => $messages,
        'watermarks' => [
            'logId' => (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM general_record') ?? 0),
            'messageId' => (int)($db->queryFirstField('SELECT COALESCE(MAX(id), 0) FROM message') ?? 0),
        ],
    ];
}

function comparisonTurnStateSnapshotMain(): void
{
    try {
        $input = stream_get_contents(STDIN);
        $request = json_decode($input === '' ? '{}' : $input, true, flags: JSON_THROW_ON_ERROR);
        if (!is_array($request)) {
            throw new \InvalidArgumentException('request must be an object');
        }
        echo json_encode(
            comparisonTurnStateSnapshot($request),
            JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION,
        ), PHP_EOL;
    } catch (\Throwable $throwable) {
        fwrite(STDERR, $throwable::class . ': ' . $throwable->getMessage() . PHP_EOL);
        exit(1);
    }
}

if (realpath((string)($_SERVER['SCRIPT_FILENAME'] ?? '')) === __FILE__) {
    comparisonTurnStateSnapshotMain();
}
