<?php

declare(strict_types=1);

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;

final class AuditLegacyArtworkUserIdsCommand extends Command
{
    protected $signature = 'artworks:audit-legacy-user-ids
                            {--chunk=1000 : Number of legacy artwork rows to process per batch}
                            {--show=100 : Maximum number of discrepancies to print}
                            {--artwork-id= : Only compare one artwork id}
                            {--legacy-connection=legacy : Legacy database connection name}
                            {--legacy-table=wallz : Legacy artworks table name}
                            {--new-table=artworks : Current artworks table name}
                            {--json : Output the summary and discrepancies as JSON}';

    protected $description = 'Compare legacy wallz.user_id values against artworks.user_id using shared artwork ids';

    public function handle(): int
    {
        $chunkSize = max(1, (int) $this->option('chunk'));
        $show = max(0, (int) $this->option('show'));
        $legacyConnection = (string) $this->option('legacy-connection');
        $legacyTable = (string) $this->option('legacy-table');
        $newTable = (string) $this->option('new-table');
        $artworkId = $this->option('artwork-id') !== null ? (int) $this->option('artwork-id') : null;
        $json = (bool) $this->option('json');

        if ($artworkId !== null && $artworkId <= 0) {
            $this->error('The --artwork-id option must be a positive integer.');

            return self::FAILURE;
        }

        if (! $this->tableExists($legacyConnection, $legacyTable)) {
            $this->error("Legacy table {$legacyConnection}.{$legacyTable} does not exist or the connection is unavailable.");

            return self::FAILURE;
        }

        if (! $this->tableExists(null, $newTable)) {
            $this->error("Current table {$newTable} does not exist.");

            return self::FAILURE;
        }

        if (! $this->columnExists($legacyConnection, $legacyTable, 'user_id') || ! $this->columnExists($legacyConnection, $legacyTable, 'id')) {
            $this->error("Legacy table {$legacyConnection}.{$legacyTable} must contain id and user_id columns.");

            return self::FAILURE;
        }

        if (! $this->columnExists(null, $newTable, 'user_id') || ! $this->columnExists(null, $newTable, 'id')) {
            $this->error("Current table {$newTable} must contain id and user_id columns.");

            return self::FAILURE;
        }

        $legacyCountQuery = DB::connection($legacyConnection)->table($legacyTable);

        if ($artworkId !== null) {
            $legacyCountQuery->where('id', $artworkId);
        }

        $total = (int) $legacyCountQuery->count();

        if ($total === 0) {
            $message = $artworkId === null
                ? "No rows found in {$legacyConnection}.{$legacyTable}."
                : "Legacy artwork #{$artworkId} was not found in {$legacyConnection}.{$legacyTable}.";

            $this->warn($message);

            return $artworkId === null ? self::SUCCESS : self::FAILURE;
        }

        $this->info(sprintf(
            'Comparing %d legacy %s.%s row(s) against %s in chunks of %d...',
            $total,
            $legacyConnection,
            $legacyTable,
            $newTable,
            $chunkSize,
        ));

        $summary = [
            'checked' => 0,
            'matched' => 0,
            'mismatched' => 0,
            'missing_in_new' => 0,
        ];
        $discrepancies = [];

        $legacyQuery = DB::connection($legacyConnection)
            ->table($legacyTable)
            ->select(['id', 'user_id'])
            ->orderBy('id');

        if ($artworkId !== null) {
            $legacyQuery->where('id', $artworkId);
        }

        $legacyQuery->chunkById($chunkSize, function ($rows) use (&$summary, &$discrepancies, $show, $newTable): void {
            $artworkIds = $rows->pluck('id')->map(static fn (mixed $id): int => (int) $id)->all();

            $newRows = DB::table($newTable)
                ->select(['id', 'user_id', 'title'])
                ->whereIn('id', $artworkIds)
                ->get()
                ->keyBy(static fn (object $row): int => (int) $row->id);

            foreach ($rows as $row) {
                $summary['checked']++;

                $legacyUserId = $this->normalizeNullableInt($row->user_id ?? null);
                $currentRow = $newRows->get((int) $row->id);

                if ($currentRow === null) {
                    $summary['missing_in_new']++;
                    $this->rememberDiscrepancy(
                        $discrepancies,
                        $show,
                        (int) $row->id,
                        $legacyUserId,
                        null,
                        null,
                        'missing_in_new',
                    );
                    continue;
                }

                $newUserId = $this->normalizeNullableInt($currentRow->user_id ?? null);

                if ($legacyUserId === $newUserId) {
                    $summary['matched']++;
                    continue;
                }

                $summary['mismatched']++;
                $this->rememberDiscrepancy(
                    $discrepancies,
                    $show,
                    (int) $row->id,
                    $legacyUserId,
                    $newUserId,
                    (string) ($currentRow->title ?? ''),
                    'user_id_mismatch',
                );
            }

            if ($this->output->isVerbose()) {
                $this->line(sprintf(
                    '  audited=%d matched=%d mismatched=%d missing_in_new=%d',
                    $summary['checked'],
                    $summary['matched'],
                    $summary['mismatched'],
                    $summary['missing_in_new'],
                ));
            }
        }, 'id');

        if ($json) {
            $this->line(json_encode([
                'summary' => $summary,
                'discrepancies' => $discrepancies,
            ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));

            return ($summary['mismatched'] === 0 && $summary['missing_in_new'] === 0)
                ? self::SUCCESS
                : self::FAILURE;
        }

        $this->table(
            ['Checked', 'Matched', 'Mismatched', 'Missing In New'],
            [[
                $summary['checked'],
                $summary['matched'],
                $summary['mismatched'],
                $summary['missing_in_new'],
            ]],
        );

        if ($discrepancies !== []) {
            $this->newLine();
            $this->warn(sprintf(
                'Showing %d discrepancy row(s)%s.',
                count($discrepancies),
                ($summary['mismatched'] + $summary['missing_in_new']) > count($discrepancies)
                    ? sprintf(' out of %d total', $summary['mismatched'] + $summary['missing_in_new'])
                    : '',
            ));

            $this->table(
                ['Artwork ID', 'Legacy user_id', 'New user_id', 'Status', 'Title'],
                array_map(static fn (array $row): array => [
                    $row['artwork_id'],
                    $row['legacy_user_id'],
                    $row['new_user_id'],
                    $row['status'],
                    $row['title'],
                ], $discrepancies),
            );
        } else {
            $this->info('No user_id mismatches were found.');
        }

        return ($summary['mismatched'] === 0 && $summary['missing_in_new'] === 0)
            ? self::SUCCESS
            : self::FAILURE;
    }

    private function tableExists(?string $connection, string $table): bool
    {
        try {
            return $connection === null
                ? DB::getSchemaBuilder()->hasTable($table)
                : DB::connection($connection)->getSchemaBuilder()->hasTable($table);
        } catch (\Throwable) {
            return false;
        }
    }

    private function columnExists(?string $connection, string $table, string $column): bool
    {
        try {
            return $connection === null
                ? DB::getSchemaBuilder()->hasColumn($table, $column)
                : DB::connection($connection)->getSchemaBuilder()->hasColumn($table, $column);
        } catch (\Throwable) {
            return false;
        }
    }

    private function normalizeNullableInt(mixed $value): ?int
    {
        if ($value === null || $value === '') {
            return null;
        }

        return (int) $value;
    }

    /**
     * @param  array<int, array{artwork_id:int, legacy_user_id:string, new_user_id:string, title:string, status:string}>  $discrepancies
     */
    private function rememberDiscrepancy(
        array &$discrepancies,
        int $show,
        int $artworkId,
        ?int $legacyUserId,
        ?int $newUserId,
        ?string $title,
        string $status,
    ): void {
        if (count($discrepancies) >= $show) {
            return;
        }

        $discrepancies[] = [
            'artwork_id' => $artworkId,
            'legacy_user_id' => $legacyUserId === null ? '[null]' : (string) $legacyUserId,
            'new_user_id' => $newUserId === null ? '[missing]' : (string) $newUserId,
            'title' => $title ?? '',
            'status' => $status,
        ];
    }
}