<?php
/**
 * EdumacFlo SaaS Custom Domain & DNS Verification Manager
 * 
 * Manages school custom domain mappings (CNAME / A records),
 * DNS verification checks, primary domain selection, and router cache invalidation.
 */

class DomainManager
{
    private mysqli $masterConn;

    public function __construct(?mysqli $masterConn = null)
    {
        if ($masterConn !== null) {
            $this->masterConn = $masterConn;
        } else {
            require_once dirname(__DIR__) . '/admin/inc/master_db.php';
            $this->masterConn = get_master_connection();
        }
    }

    /**
     * Clean and normalize domain name.
     */
    public function normalizeDomain(string $domain): string
    {
        $d = trim(strtolower($domain));
        // Remove scheme
        $d = preg_replace('#^https?://#i', '', $d);
        // Remove trailing path or slash
        $d = explode('/', $d)[0];
        // Remove port
        $d = preg_replace('/:\d+$/', '', $d);
        return $d;
    }

    /**
     * Validate domain syntax.
     */
    public function isValidDomain(string $domain): bool
    {
        $d = $this->normalizeDomain($domain);
        if (empty($d) || strlen($d) > 190) return false;
        // Accept localhost, IP addresses, or standard domain names
        if ($d === 'localhost' || filter_var($d, FILTER_VALIDATE_IP)) return true;
        return (bool)preg_match('/^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\.[A-Za-z0-9-]{1,63})*\.[A-Za-z]{2,63}$/', $d);
    }

    /**
     * Inspect DNS records for a domain (A and CNAME).
     */
    public function checkDns(string $domain): array
    {
        $d = $this->normalizeDomain($domain);
        $records = [
            'domain'    => $d,
            'cname'     => [],
            'a'         => [],
            'resolves'  => false,
        ];

        if ($d === 'localhost' || $d === '127.0.0.1' || str_ends_with($d, '.local')) {
            $records['a'][] = '127.0.0.1';
            $records['resolves'] = true;
            return $records;
        }

        if (function_exists('dns_get_record')) {
            $cnameRes = @dns_get_record($d, DNS_CNAME);
            if (!empty($cnameRes)) {
                foreach ($cnameRes as $r) {
                    if (!empty($r['target'])) $records['cname'][] = $r['target'];
                }
            }

            $aRes = @dns_get_record($d, DNS_A);
            if (!empty($aRes)) {
                foreach ($aRes as $r) {
                    if (!empty($r['ip'])) $records['a'][] = $r['ip'];
                }
            }
        }

        $records['resolves'] = (!empty($records['cname']) || !empty($records['a']));
        return $records;
    }

    /**
     * Add a domain to a tenant.
     */
    public function addDomain(int $tenantId, string $domain, bool $isPrimary = false, bool $isVerified = false): array
    {
        $d = $this->normalizeDomain($domain);
        if (!$this->isValidDomain($d)) {
            return ['success' => false, 'message' => "Invalid domain name format: '{$domain}'."];
        }

        // Check if tenant exists
        $tStmt = $this->masterConn->prepare("SELECT * FROM `tenants` WHERE `id` = ? LIMIT 1");
        $tStmt->bind_param("i", $tenantId);
        $tStmt->execute();
        $tenant = $tStmt->get_result()->fetch_assoc();
        $tStmt->close();

        if (!$tenant) {
            return ['success' => false, 'message' => "Tenant ID #{$tenantId} not found."];
        }

        // Check if domain is already registered anywhere
        $checkStmt = $this->masterConn->prepare("SELECT id, tenant_id FROM `tenant_domains` WHERE `domain` = ? LIMIT 1");
        $checkStmt->bind_param("s", $d);
        $checkStmt->execute();
        $existing = $checkStmt->get_result()->fetch_assoc();
        $checkStmt->close();

        if ($existing) {
            if ((int)$existing['tenant_id'] === $tenantId) {
                return ['success' => false, 'message' => "Domain '{$d}' is already assigned to this school."];
            }
            return ['success' => false, 'message' => "Domain '{$d}' is already mapped to another school on the platform."];
        }

        // If setting as primary, demote other domains for this tenant
        if ($isPrimary) {
            $demoteStmt = $this->masterConn->prepare("UPDATE `tenant_domains` SET `is_primary` = 0 WHERE `tenant_id` = ?");
            $demoteStmt->bind_param("i", $tenantId);
            $demoteStmt->execute();
            $demoteStmt->close();
        }

        $primaryInt  = $isPrimary ? 1 : 0;
        $verifiedInt = $isVerified ? 1 : 0;

        $insStmt = $this->masterConn->prepare(
            "INSERT INTO `tenant_domains` (`tenant_id`, `domain`, `is_primary`, `is_verified`, `created_at`) 
             VALUES (?, ?, ?, ?, NOW())"
        );
        $insStmt->bind_param("isii", $tenantId, $d, $primaryInt, $verifiedInt);
        $ok = $insStmt->execute();
        $domainId = $insStmt->insert_id;
        $insStmt->close();

        if (!$ok) {
            return ['success' => false, 'message' => 'Failed to save domain: ' . $this->masterConn->error];
        }

        $this->invalidateCache($tenant, $d);

        return [
            'success'     => true,
            'domain_id'   => $domainId,
            'domain'      => $d,
            'is_primary'  => $isPrimary,
            'is_verified' => $isVerified,
            'message'     => "Domain '{$d}' mapped successfully" . ($isVerified ? ' (verified).' : ' (pending DNS verification).'),
        ];
    }

    /**
     * Verify domain DNS and mark active.
     */
    public function verifyDomain(int $domainId, bool $force = false): array
    {
        $stmt = $this->masterConn->prepare("SELECT d.*, t.slug, t.school_name FROM `tenant_domains` d JOIN `tenants` t ON d.tenant_id = t.id WHERE d.id = ? LIMIT 1");
        $stmt->bind_param("i", $domainId);
        $stmt->execute();
        $record = $stmt->get_result()->fetch_assoc();
        $stmt->close();

        if (!$record) {
            return ['success' => false, 'message' => 'Domain record not found.'];
        }

        $domain = $record['domain'];
        $dnsInfo = $this->checkDns($domain);

        // Auto verify if forced, or if localhost, or if DNS resolves
        $canVerify = ($force || $dnsInfo['resolves']);

        if ($canVerify) {
            $upStmt = $this->masterConn->prepare("UPDATE `tenant_domains` SET `is_verified` = 1 WHERE `id` = ?");
            $upStmt->bind_param("i", $domainId);
            $upStmt->execute();
            $upStmt->close();

            $this->invalidateCache(['id' => $record['tenant_id'], 'slug' => $record['slug']], $domain);

            return [
                'success'     => true,
                'is_verified' => true,
                'domain'      => $domain,
                'dns'         => $dnsInfo,
                'message'     => "Domain '{$domain}' verified successfully and active for routing.",
            ];
        }

        return [
            'success'     => false,
            'is_verified' => false,
            'domain'      => $domain,
            'dns'         => $dnsInfo,
            'message'     => "DNS verification failed for '{$domain}'. Ensure CNAME or A records are properly pointed.",
        ];
    }

    /**
     * Set a domain as the primary domain for a school.
     */
    public function setPrimaryDomain(int $tenantId, int $domainId): array
    {
        // Demote all
        $dStmt = $this->masterConn->prepare("UPDATE `tenant_domains` SET `is_primary` = 0 WHERE `tenant_id` = ?");
        $dStmt->bind_param("i", $tenantId);
        $dStmt->execute();
        $dStmt->close();

        // Promote selected
        $pStmt = $this->masterConn->prepare("UPDATE `tenant_domains` SET `is_primary` = 1 WHERE `id` = ? AND `tenant_id` = ?");
        $pStmt->bind_param("ii", $domainId, $tenantId);
        $pStmt->execute();
        $pStmt->close();

        // Invalidate cache
        $tStmt = $this->masterConn->prepare("SELECT * FROM `tenants` WHERE `id` = ? LIMIT 1");
        $tStmt->bind_param("i", $tenantId);
        $tStmt->execute();
        $tenant = $tStmt->get_result()->fetch_assoc();
        $tStmt->close();

        if ($tenant) {
            $this->invalidateTenantCache($tenant);
        }

        return ['success' => true, 'message' => 'Primary domain updated.'];
    }

    /**
     * Delete custom domain (non-primary only).
     */
    public function deleteDomain(int $tenantId, int $domainId): array
    {
        // Check primary status
        $cStmt = $this->masterConn->prepare("SELECT * FROM `tenant_domains` WHERE `id` = ? AND `tenant_id` = ? LIMIT 1");
        $cStmt->bind_param("ii", $domainId, $tenantId);
        $cStmt->execute();
        $dom = $cStmt->get_result()->fetch_assoc();
        $cStmt->close();

        if (!$dom) {
            return ['success' => false, 'message' => 'Domain not found.'];
        }

        if (!empty($dom['is_primary'])) {
            return ['success' => false, 'message' => 'Cannot delete the primary school domain. Assign a new primary first.'];
        }

        $delStmt = $this->masterConn->prepare("DELETE FROM `tenant_domains` WHERE `id` = ? AND `tenant_id` = ?");
        $delStmt->bind_param("ii", $domainId, $tenantId);
        $delStmt->execute();
        $delStmt->close();

        $this->invalidateCache(['id' => $tenantId], $dom['domain']);

        return ['success' => true, 'message' => "Domain '{$dom['domain']}' removed."];
    }

    /**
     * Clear router cache for a specific domain.
     */
    public function invalidateCache(array $tenant, string $domain): void
    {
        $cacheDir = dirname(__DIR__, 2) . '/public/cache/tenants';
        if (!is_dir($cacheDir)) return;

        $domFile = $cacheDir . '/tenant_' . md5(strtolower(trim($domain))) . '.json';
        if (file_exists($domFile)) @unlink($domFile);

        if (!empty($tenant['slug'])) {
            $slugFile = $cacheDir . '/tenant_' . md5(strtolower(trim($tenant['slug']))) . '.json';
            if (file_exists($slugFile)) @unlink($slugFile);
        }
    }

    /**
     * Invalidate all domain cache files for a tenant.
     */
    public function invalidateTenantCache(array $tenant): void
    {
        $cacheDir = dirname(__DIR__, 2) . '/public/cache/tenants';
        if (!is_dir($cacheDir)) return;

        $tId = (int)$tenant['id'];
        $res = mysqli_query($this->masterConn, "SELECT `domain` FROM `tenant_domains` WHERE `tenant_id` = {$tId}");
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $domFile = $cacheDir . '/tenant_' . md5(strtolower(trim($row['domain']))) . '.json';
                if (file_exists($domFile)) @unlink($domFile);
            }
        }
    }

    /**
     * Get aggregate statistics.
     */
    public function getStats(): array
    {
        $totalRes = mysqli_query($this->masterConn, "SELECT COUNT(*) FROM `tenant_domains`");
        $totalRow = mysqli_fetch_row($totalRes);

        $verRes = mysqli_query($this->masterConn, "SELECT COUNT(*) FROM `tenant_domains` WHERE `is_verified` = 1");
        $verRow = mysqli_fetch_row($verRes);

        $pendRes = mysqli_query($this->masterConn, "SELECT COUNT(*) FROM `tenant_domains` WHERE `is_verified` = 0");
        $pendRow = mysqli_fetch_row($pendRes);

        $primRes = mysqli_query($this->masterConn, "SELECT COUNT(*) FROM `tenant_domains` WHERE `is_primary` = 1");
        $primRow = mysqli_fetch_row($primRes);

        $custRes = mysqli_query($this->masterConn, "SELECT COUNT(DISTINCT tenant_id) FROM `tenant_domains` WHERE `domain` NOT LIKE '%.edumacflo.com' AND `domain` != 'localhost'");
        $custRow = mysqli_fetch_row($custRes);

        return [
            'total'          => (int)($totalRow[0] ?? 0),
            'verified'       => (int)($verRow[0] ?? 0),
            'pending'        => (int)($pendRow[0] ?? 0),
            'primary'        => (int)($primRow[0] ?? 0),
            'custom_schools' => (int)($custRow[0] ?? 0),
        ];
    }

    /**
     * Get all mapped domains with pagination.
     */
    public function getAllDomains(int $limit = 50, int $offset = 0, ?string $search = null): array
    {
        $where = "1=1";
        if (!empty($search)) {
            $esc = mysqli_real_escape_string($this->masterConn, $search);
            $where .= " AND (d.domain LIKE '%{$esc}%' OR t.school_name LIKE '%{$esc}%' OR t.slug LIKE '%{$esc}%')";
        }

        $cRes = mysqli_query($this->masterConn, "SELECT COUNT(*) FROM `tenant_domains` d JOIN `tenants` t ON d.tenant_id = t.id WHERE {$where}");
        $cRow = mysqli_fetch_row($cRes);
        $total = (int)($cRow[0] ?? 0);

        $sql = "SELECT d.*, t.school_name, t.slug AS tenant_slug, t.status AS tenant_status 
                FROM `tenant_domains` d 
                JOIN `tenants` t ON d.tenant_id = t.id 
                WHERE {$where} 
                ORDER BY d.is_primary DESC, d.id DESC 
                LIMIT {$offset}, {$limit}";

        $res = mysqli_query($this->masterConn, $sql);
        $items = [];
        if ($res) {
            while ($row = mysqli_fetch_assoc($res)) {
                $items[] = $row;
            }
        }

        return ['total' => $total, 'items' => $items];
    }
}
