<?php
/**
 * 站点域名注册表（dashboard 管理，ip_check 按 Host 解析配置）
 */
class DomainRepository
{
    public static function ensureTable(PDO $pdo)
    {
        $pdo->exec(
            "CREATE TABLE IF NOT EXISTS `cloak_site_domains` (
                `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT,
                `hostname` varchar(255) NOT NULL COMMENT '站点域名，不含协议与路径',
                `config_name` varchar(64) NOT NULL DEFAULT '' COMMENT '关联 check_config 配置名',
                `cf_zone_id` varchar(64) DEFAULT NULL COMMENT 'Cloudflare Zone ID',
                `cf_nameservers` text DEFAULT NULL COMMENT 'Cloudflare NS JSON',
                `cf_status` varchar(32) NOT NULL DEFAULT 'local' COMMENT 'Cloudflare 状态',
                `cf_error` varchar(500) DEFAULT NULL COMMENT '最近错误',
                `cf_checked_at` datetime DEFAULT NULL COMMENT '上次检测时间',
                `created_at` timestamp NOT NULL DEFAULT current_timestamp(),
                PRIMARY KEY (`id`),
                UNIQUE KEY `uk_hostname` (`hostname`),
                KEY `idx_config_name` (`config_name`)
            ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci"
        );
        self::ensureCloudflareColumns($pdo);
    }

    private static function ensureCloudflareColumns(PDO $pdo)
    {
        $columns = [
            'cf_zone_id'     => "ADD COLUMN `cf_zone_id` varchar(64) DEFAULT NULL COMMENT 'Cloudflare Zone ID'",
            'cf_nameservers' => "ADD COLUMN `cf_nameservers` text DEFAULT NULL COMMENT 'Cloudflare NS JSON'",
            'cf_status'      => "ADD COLUMN `cf_status` varchar(32) NOT NULL DEFAULT 'local' COMMENT 'Cloudflare 状态'",
            'cf_error'       => "ADD COLUMN `cf_error` varchar(500) DEFAULT NULL COMMENT '最近错误'",
            'cf_checked_at'  => "ADD COLUMN `cf_checked_at` datetime DEFAULT NULL COMMENT '上次检测时间'",
        ];
        foreach ($columns as $name => $ddl) {
            if (!self::columnExists($pdo, 'cloak_site_domains', $name)) {
                $pdo->exec('ALTER TABLE `cloak_site_domains` ' . $ddl);
            }
        }
    }

    private static function columnExists(PDO $pdo, $table, $column)
    {
        $st = $pdo->prepare(
            'SELECT COUNT(*) FROM information_schema.COLUMNS
             WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND COLUMN_NAME = ?'
        );
        $st->execute([DB_NAME, $table, $column]);
        return (int) $st->fetchColumn() > 0;
    }

    private static function selectColumns()
    {
        return 'id, hostname, config_name, cf_zone_id, cf_nameservers, cf_status, cf_error, cf_checked_at, created_at';
    }

    public static function pdo()
    {
        return new PDO(
            'mysql:host=127.0.0.1;dbname=' . DB_NAME . ';charset=utf8mb4',
            DB_USERNAME,
            DB_PASSWORD,
            [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC]
        );
    }

    public static function normalizeHostname($host)
    {
        $host = trim((string) $host);
        if ($host === '') {
            return '';
        }
        if (strpos($host, '://') !== false) {
            $parsed = parse_url($host, PHP_URL_HOST);
            $host   = $parsed ?: $host;
        } else {
            $host = preg_replace('#/.*$#', '', $host);
            $host = preg_replace('#:\d+$#', '', $host);
        }
        $host = strtolower(trim($host, '.'));
        if (str_starts_with($host, 'www.')) {
            $host = substr($host, 4);
        }
        return $host;
    }

    public static function hostToConfigKey($host)
    {
        $host = self::normalizeHostname($host);
        if ($host === '') {
            return 'index';
        }
        $key = preg_replace('/[^a-z0-9_-]+/i', '_', $host);
        $key = trim($key, '_');
        return $key !== '' ? $key : 'index';
    }

    public static function listAll(PDO $pdo = null)
    {
        $pdo = $pdo ?: self::pdo();
        self::ensureTable($pdo);
        $st  = $pdo->query('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains ORDER BY hostname ASC');
        return $st->fetchAll();
    }

    public static function findById(PDO $pdo, $id)
    {
        self::ensureTable($pdo);
        $st = $pdo->prepare('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains WHERE id = ? LIMIT 1');
        $st->execute([(int) $id]);
        $row = $st->fetch();
        return $row ?: null;
    }

    public static function updateCloudflareMeta(PDO $pdo, $id, array $fields)
    {
        self::ensureTable($pdo);
        $allowed = ['cf_zone_id', 'cf_nameservers', 'cf_status', 'cf_error', 'cf_checked_at'];
        $sets    = [];
        $vals    = [];
        foreach ($allowed as $key) {
            if (array_key_exists($key, $fields)) {
                $sets[] = "`{$key}` = ?";
                $vals[] = $fields[$key];
            }
        }
        if ($sets === []) {
            return false;
        }
        $vals[] = (int) $id;
        $sql    = 'UPDATE cloak_site_domains SET ' . implode(', ', $sets) . ' WHERE id = ?';
        $st     = $pdo->prepare($sql);
        $st->execute($vals);
        return $st->rowCount() > 0;
    }

    public static function findByHostname(PDO $pdo, $hostname)
    {
        self::ensureTable($pdo);
        $host = self::normalizeHostname($hostname);
        if ($host === '') {
            return null;
        }
        $st = $pdo->prepare('SELECT ' . self::selectColumns() . ' FROM cloak_site_domains WHERE hostname = ? LIMIT 1');
        $st->execute([$host]);
        $row = $st->fetch();
        return $row ?: null;
    }

    public static function add(PDO $pdo, $hostname)
    {
        self::ensureTable($pdo);
        $host = self::normalizeHostname($hostname);
        if ($host === '' || !preg_match('/^[a-z0-9]([a-z0-9.-]*[a-z0-9])?$/i', $host)) {
            return ['ok' => false, 'error' => '域名格式无效'];
        }
        try {
            $st = $pdo->prepare('INSERT INTO cloak_site_domains (hostname, config_name) VALUES (?, ?)');
            $st->execute([$host, '']);
            return ['ok' => true, 'id' => (int) $pdo->lastInsertId(), 'hostname' => $host];
        } catch (PDOException $e) {
            if ((int) $e->getCode() === 23000) {
                return ['ok' => false, 'error' => '该域名已存在'];
            }
            throw $e;
        }
    }

    public static function deleteById(PDO $pdo, $id)
    {
        self::ensureTable($pdo);
        $st = $pdo->prepare('DELETE FROM cloak_site_domains WHERE id = ?');
        $st->execute([(int) $id]);
        return $st->rowCount() > 0;
    }

    public static function bindConfigToDomain(PDO $pdo, $configName, $domainId)
    {
        self::ensureTable($pdo);
        $configName = trim($configName);
        $domainId   = (int) $domainId;
        if ($configName === '' || $domainId <= 0) {
            return false;
        }
        $pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE config_name = ?")->execute([$configName]);
        $st = $pdo->prepare('UPDATE cloak_site_domains SET config_name = ? WHERE id = ?');
        $st->execute([$configName, $domainId]);
        return $st->rowCount() > 0;
    }

    public static function clearConfigBinding(PDO $pdo, $configName)
    {
        self::ensureTable($pdo);
        $st = $pdo->prepare("UPDATE cloak_site_domains SET config_name = '' WHERE config_name = ?");
        $st->execute([trim($configName)]);
    }

    public static function domainIdForConfig(PDO $pdo, $configName)
    {
        self::ensureTable($pdo);
        $st = $pdo->prepare('SELECT id FROM cloak_site_domains WHERE config_name = ? ORDER BY id ASC LIMIT 1');
        $st->execute([trim($configName)]);
        $row = $st->fetch();
        return $row ? (int) $row['id'] : 0;
    }

    public static function primaryHostnameForConfig(PDO $pdo, $configName)
    {
        self::ensureTable($pdo);
        $st = $pdo->prepare('SELECT hostname FROM cloak_site_domains WHERE config_name = ? ORDER BY id ASC LIMIT 1');
        $st->execute([trim($configName)]);
        $row = $st->fetch();
        return $row ? $row['hostname'] : '';
    }

    public static function resolveConfigNameForHost($host, $baseDir = null)
    {
        $baseDir = $baseDir ?: dirname(__DIR__, 2);
        $norm    = self::normalizeHostname($host);
        if ($norm !== '') {
            try {
                $pdo = self::pdo();
                $row = self::findByHostname($pdo, $norm);
                if ($row && !empty($row['config_name'])) {
                    $name = trim($row['config_name']);
                    if (file_exists($baseDir . '/check_config/' . $name . '_config.php')) {
                        return $name;
                    }
                }
            } catch (Throwable $e) {
            }
            $key = self::hostToConfigKey($norm);
            if (file_exists($baseDir . '/check_config/' . $key . '_config.php')) {
                return $key;
            }
        }
        return 'index';
    }
}
