<?php

declare(strict_types=1);

namespace App\Services\Install;

use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\DB;
use Throwable;

class InstallerDatabaseStateChecker
{
    private const TEMP_CONNECTION = 'dberp_install_state_check';

    /**
     * @return array{
     *     safe: bool,
     *     summary: string,
     *     details: list<string>,
     *     checks: array{
     *         database_empty: bool,
     *         migrations_table_exists: bool,
     *         core_tables_exist: bool,
     *         all_table_count: int,
     *         view_count: int,
     *         core_table_count: int,
     *         migration_record_count: int,
     *         erp_migration_record_count: int,
     *         reinstall_reset_supported: bool
     *     }
     * }
     */
    public function inspectCurrentConnection(): array
    {
        $defaultConnection = (string) config('database.default', 'mysql');

        return $this->inspectByConnection($defaultConnection);
    }

    public function classifyWizardDatabaseState(bool $applicationMarkedInstalled): WebInstallDatabaseSituation
    {
        return $this->classifyFromInspection($this->inspectCurrentConnection(), $applicationMarkedInstalled);
    }

    /**
     * @param  array{
     *     safe: bool,
     *     checks: array{
     *         migrations_table_exists?: bool,
     *         migration_record_count?: int,
     *         core_tables_exist?: bool,
     *         erp_migration_record_count?: int,
     *     }
     * }  $databaseState
     */
    public function classifyFromInspection(array $databaseState, bool $applicationMarkedInstalled): WebInstallDatabaseSituation
    {
        if ($applicationMarkedInstalled) {
            return WebInstallDatabaseSituation::NotInstallable;
        }

        if ($databaseState['safe']) {
            $checks = $databaseState['checks'];

            if (($checks['migrations_table_exists'] ?? false) && ($checks['migration_record_count'] ?? 0) === 0) {
                return WebInstallDatabaseSituation::EmptyMigrationsTableOnly;
            }

            return WebInstallDatabaseSituation::EmptySafe;
        }

        $checks = $databaseState['checks'];

        if (($checks['core_tables_exist'] ?? false) && ($checks['erp_migration_record_count'] ?? 0) > 0) {
            return WebInstallDatabaseSituation::PartialInstallation;
        }

        return WebInstallDatabaseSituation::NotInstallable;
    }

    /**
     * @return array{
     *     safe: bool,
     *     summary: string,
     *     details: list<string>,
     *     checks: array{
     *         database_empty: bool,
     *         migrations_table_exists: bool,
     *         core_tables_exist: bool,
     *         all_table_count: int,
     *         view_count: int,
     *         core_table_count: int,
     *         migration_record_count: int,
     *         erp_migration_record_count: int,
     *         reinstall_reset_supported: bool
     *     }
     * }
     */
    public function inspectMysqlFamily(
        string $driver,
        string $host,
        int $port,
        string $database,
        string $username,
        #[\SensitiveParameter]
        ?string $password,
        string $tablePrefix = '',
    ): array {
        $password ??= '';

        $templateKey = $driver === 'mariadb' ? 'mariadb' : 'mysql';
        $template = (array) config('database.connections.'.$templateKey, []);

        $config = array_merge($template, [
            'driver' => $driver === 'mariadb' ? 'mariadb' : 'mysql',
            'host' => $host,
            'port' => (string) $port,
            'database' => $database,
            'username' => $username,
            'password' => $password,
            'prefix' => $tablePrefix,
        ]);

        Config::set('database.connections.'.self::TEMP_CONNECTION, $config);
        DB::purge(self::TEMP_CONNECTION);

        try {
            return $this->inspectByConnection(self::TEMP_CONNECTION);
        } finally {
            DB::disconnect(self::TEMP_CONNECTION);
            DB::purge(self::TEMP_CONNECTION);
            $this->removeTemporaryConnectionConfig();
        }
    }

    /**
     * @return array{
     *     safe: bool,
     *     summary: string,
     *     details: list<string>,
     *     checks: array{
     *         database_empty: bool,
     *         migrations_table_exists: bool,
     *         core_tables_exist: bool,
     *         all_table_count: int,
     *         view_count: int,
     *         core_table_count: int,
     *         migration_record_count: int,
     *         erp_migration_record_count: int,
     *         reinstall_reset_supported: bool
     *     }
     * }
     */
    private function inspectByConnection(string $connectionName): array
    {
        try {
            $driver = DB::connection($connectionName)->getDriverName();

            if (! in_array($driver, ['mysql', 'mariadb'], true)) {
                return $this->state(
                    safe: false,
                    summary: '当前数据库驱动不受安装向导支持。',
                    details: ['请使用 MySQL 或 MariaDB 作为安装目标数据库。'],
                    databaseEmpty: false,
                    migrationsTableExists: false,
                    coreTablesExist: false,
                    allTableCount: 0,
                    viewCount: 0,
                    coreTableCount: 0,
                    migrationRecordCount: 0,
                    erpMigrationRecordCount: 0,
                    reinstallResetSupported: false,
                );
            }

            $schema = $this->fetchMysqlBaseTablesAndViews($connectionName);
            $baseTables = $schema['base_tables'];
            $views = $schema['views'];

            $baseTableCount = count($baseTables);
            $viewCount = count($views);
            $databaseEmpty = $baseTableCount === 0 && $viewCount === 0;

            $migrationsTable = (string) config('database.migrations.table', 'migrations');
            $migrationsTableExists = in_array($migrationsTable, $baseTables, true);

            $erpObjectNames = array_values(array_filter(
                array_merge($baseTables, $views),
                static fn (string $name): bool => InstallerHostedSchemaCatalog::isErpPrefixed($name)
            ));
            $coreTablesExist = $erpObjectNames !== [];
            $coreTableCount = count($erpObjectNames);

            $hasForeignSchemaObjects = $this->hasForeignSchemaObjects($baseTables, $views);
            $reinstallResetSupported = ! $hasForeignSchemaObjects;

            $migrationRecordCount = 0;
            $erpMigrationRecordCount = 0;

            if ($migrationsTableExists) {
                $migrationRecordCount = (int) DB::connection($connectionName)->table($migrationsTable)->count();
                $erpMigrationRecordCount = (int) DB::connection($connectionName)
                    ->table($migrationsTable)
                    ->where(static function ($query): void {
                        $query->where('migration', 'like', '%dberp%')
                            ->orWhere('migration', 'like', '%core_permission%')
                            ->orWhere('migration', 'like', '%user_permission_grants%');
                    })
                    ->count();
            }

            if ($databaseEmpty) {
                return $this->state(
                    safe: true,
                    summary: '数据库为空，可以开始安装。',
                    details: ['当前库为空，可以开始安装。'],
                    databaseEmpty: true,
                    migrationsTableExists: false,
                    coreTablesExist: false,
                    allTableCount: 0,
                    viewCount: 0,
                    coreTableCount: 0,
                    migrationRecordCount: 0,
                    erpMigrationRecordCount: 0,
                    reinstallResetSupported: $reinstallResetSupported,
                );
            }

            if ($baseTableCount === 1 && $migrationsTableExists && $migrationRecordCount === 0 && $viewCount === 0) {
                return $this->state(
                    safe: true,
                    summary: '数据库几乎为空，可以继续安装。',
                    details: ['未发现业务数据，可以继续完成安装。'],
                    databaseEmpty: false,
                    migrationsTableExists: true,
                    coreTablesExist: false,
                    allTableCount: 1,
                    viewCount: 0,
                    coreTableCount: 0,
                    migrationRecordCount: 0,
                    erpMigrationRecordCount: 0,
                    reinstallResetSupported: $reinstallResetSupported,
                );
            }

            $unsafeReasons = [];

            if ($coreTablesExist && ! $migrationsTableExists) {
                $unsafeReasons[] = '数据库中已有本系统相关数据，但缺少完整安装信息，无法确认能否安全继续。';
            }

            if ($coreTablesExist && $migrationsTableExists && $erpMigrationRecordCount === 0) {
                $unsafeReasons[] = '数据库中的业务数据与安装信息不一致，继续安装可能失败或覆盖已有内容。';
            }

            if (! $coreTablesExist && $migrationsTableExists && $erpMigrationRecordCount > 0) {
                $unsafeReasons[] = '存在安装信息但缺少关键业务数据，数据库状态异常。';
            }

            if (! $coreTablesExist && ! $migrationsTableExists && ($baseTableCount > 0 || $viewCount > 0)) {
                $unsafeReasons[] = '当前数据库已被其他系统使用，请选择专用空库后再安装。';
            }

            if (! $coreTablesExist && $migrationsTableExists && $migrationRecordCount > 0 && $erpMigrationRecordCount === 0) {
                $unsafeReasons[] = '当前数据库含有其他系统的历史安装信息，无法保证与本系统兼容。';
            }

            if ($coreTablesExist && $erpMigrationRecordCount > 0) {
                $unsafeReasons[] = '检测到本系统可能已安装或曾部分安装，请勿重复安装。';
            }

            if ($unsafeReasons === []) {
                $unsafeReasons[] = '无法确认数据库为全新空库，请更换专用空库后再试。';
            }

            return $this->state(
                safe: false,
                summary: '数据库状态不满足安装条件。',
                details: $unsafeReasons,
                databaseEmpty: false,
                migrationsTableExists: $migrationsTableExists,
                coreTablesExist: $coreTablesExist,
                allTableCount: $baseTableCount,
                viewCount: $viewCount,
                coreTableCount: $coreTableCount,
                migrationRecordCount: $migrationRecordCount,
                erpMigrationRecordCount: $erpMigrationRecordCount,
                reinstallResetSupported: $reinstallResetSupported,
            );
        } catch (Throwable) {
            return $this->state(
                safe: false,
                summary: '无法完成数据库状态检查，请先确认连接信息无误。',
                details: ['系统暂时无法读取数据库结构，请返回上一步重新测试数据库连接。'],
                databaseEmpty: false,
                migrationsTableExists: false,
                coreTablesExist: false,
                allTableCount: 0,
                viewCount: 0,
                coreTableCount: 0,
                migrationRecordCount: 0,
                erpMigrationRecordCount: 0,
                reinstallResetSupported: false,
            );
        }
    }

    /**
     * @param  list<string>  $baseTables
     * @param  list<string>  $views
     */
    private function hasForeignSchemaObjects(array $baseTables, array $views): bool
    {
        foreach ($views as $viewName) {
            if (! InstallerHostedSchemaCatalog::isHostOwnedView($viewName)) {
                return true;
            }
        }

        foreach ($baseTables as $tableName) {
            if (! InstallerHostedSchemaCatalog::isHostOwnedBaseTable($tableName)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return array{base_tables: list<string>, views: list<string>}
     */
    private function fetchMysqlBaseTablesAndViews(string $connectionName): array
    {
        $connection = DB::connection($connectionName);
        $databaseName = (string) ($connection->getDatabaseName() ?: config('database.connections.'.$connectionName.'.database', ''));

        if ($databaseName === '') {
            return ['base_tables' => [], 'views' => []];
        }

        $rows = $connection->select(
            "SELECT table_name AS table_name, table_type AS table_type
             FROM information_schema.tables
             WHERE table_schema = ?
               AND table_type IN ('BASE TABLE', 'VIEW')",
            [$databaseName]
        );

        $baseTables = [];
        $views = [];

        foreach ($rows as $row) {
            $tableName = (string) ($row->table_name ?? $row->TABLE_NAME ?? '');
            $tableType = (string) ($row->table_type ?? $row->TABLE_TYPE ?? '');

            if ($tableName === '') {
                continue;
            }

            if ($tableType === 'VIEW') {
                $views[] = $tableName;
            } elseif ($tableType === 'BASE TABLE') {
                $baseTables[] = $tableName;
            }
        }

        $baseTables = array_values(array_unique($baseTables));
        $views = array_values(array_unique($views));
        sort($baseTables, SORT_STRING);
        sort($views, SORT_STRING);

        return ['base_tables' => $baseTables, 'views' => $views];
    }

    /**
     * @return array{
     *     safe: bool,
     *     summary: string,
     *     details: list<string>,
     *     checks: array{
     *         database_empty: bool,
     *         migrations_table_exists: bool,
     *         core_tables_exist: bool,
     *         all_table_count: int,
     *         view_count: int,
     *         core_table_count: int,
     *         migration_record_count: int,
     *         erp_migration_record_count: int,
     *         reinstall_reset_supported: bool
     *     }
     * }
     */
    private function state(
        bool $safe,
        string $summary,
        array $details,
        bool $databaseEmpty,
        bool $migrationsTableExists,
        bool $coreTablesExist,
        int $allTableCount,
        int $viewCount,
        int $coreTableCount,
        int $migrationRecordCount,
        int $erpMigrationRecordCount,
        bool $reinstallResetSupported,
    ): array {
        return [
            'safe' => $safe,
            'summary' => $summary,
            'details' => $details,
            'checks' => [
                'database_empty' => $databaseEmpty,
                'migrations_table_exists' => $migrationsTableExists,
                'core_tables_exist' => $coreTablesExist,
                'all_table_count' => $allTableCount,
                'view_count' => $viewCount,
                'core_table_count' => $coreTableCount,
                'migration_record_count' => $migrationRecordCount,
                'erp_migration_record_count' => $erpMigrationRecordCount,
                'reinstall_reset_supported' => $reinstallResetSupported,
            ],
        ];
    }

    private function removeTemporaryConnectionConfig(): void
    {
        $connections = (array) Config::get('database.connections', []);
        unset($connections[self::TEMP_CONNECTION]);
        Config::set('database.connections', $connections);
    }
}
