<?php
// Domains & DNS AJAX handler — JSON envelope: {error} on failure, {success:true,...} on success.
error_reporting(E_ALL);
ini_set('display_errors', 0);
ini_set('log_errors', 1);
ob_start();

require_once('../auto-prepend.php');

function dns_json($payload, int $status = 200) {
    if (ob_get_length() !== false) { ob_clean(); }
    http_response_code($status);
    header('Content-Type: application/json');
    echo json_encode($payload);
    exit();
}

if (!defined('AUTH_USER')) {
    dns_json(['error' => 'Not authenticated'], 401);
}

require_once('../libs/mysqlmgmt.php');
require_once('../libs/pdns-integration.php');
require_once('../libs/dns_record_validator.php');

// CSRF on every mutating request (list is read-only, allow GET/POST without token).
$action = $_REQUEST['action'] ?? '';
$mutating = !in_array($action, ['list_records'], true);
if ($mutating) {
    if (!validate_csrf_token()) {
        dns_json(['error' => 'Invalid CSRF token'], 403);
    }
}

$is_root = (AUTH_USER === 'root');
$MySQLMgmt = new mysqlmgmt();
$db = $MySQLMgmt->getMySQLConnection();
if (!$db) { dns_json(['error' => 'Database connection failed'], 500); }

try { $pdns = new PowerDNSIntegration($db); }
catch (Exception $e) { dns_json(['error' => 'PowerDNS unavailable: ' . $e->getMessage()], 500); }

/** Throwing ownership gate: returns the domain row or sends a JSON error. */
function dns_require_domain_access($db, string $domain_name, bool $is_root): array {
    $stmt = $db->prepare("SELECT id, domain_name, username FROM whp.domains WHERE domain_name = ?");
    $stmt->execute([$domain_name]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$row) { dns_json(['error' => 'Domain not found'], 404); }
    if (!$is_root && $row['username'] !== AUTH_USER) {
        dns_json(['error' => 'You can only manage your own domains'], 403);
    }
    return $row;
}

switch ($action) {
    case 'list_records': {
        $domain_name = trim($_REQUEST['domain_name'] ?? '');
        dns_require_domain_access($db, $domain_name, $is_root);
        $records = $pdns->getRecords($domain_name);
        if ($records === false) { dns_json(['error' => 'Failed to load DNS records'], 500); }
        // Split priority out of MX/SRV content for the UI.
        $out = [];
        foreach ($records as $r) {
            $priority = null; $content = $r['content'];
            if (in_array($r['type'], ['MX','SRV'])) {
                $parts = explode(' ', $r['content'], 2);
                if (count($parts) === 2 && is_numeric($parts[0])) { $priority = (int)$parts[0]; $content = $parts[1]; }
            }
            $out[] = [
                'name' => $r['name'], 'type' => $r['type'], 'content' => $content,
                'raw_content' => $r['content'], 'priority' => $priority,
                'ttl' => (int)$r['ttl'], 'disabled' => !empty($r['disabled']),
            ];
        }
        dns_json(['success' => true, 'records' => $out]);
    }

    case 'add_record':
    case 'edit_record': {
        $domain_name = trim($_POST['domain_name'] ?? '');
        dns_require_domain_access($db, $domain_name, $is_root);
        $type = strtoupper(trim($_POST['record_type'] ?? ''));
        $name = trim($_POST['record_name'] ?? '');
        $content = trim($_POST['record_content'] ?? '');
        $ttl = dns_record_normalize_ttl($_POST['ttl'] ?? 300);
        $priority = ($_POST['priority'] ?? '') === '' ? null : (int)$_POST['priority'];

        $v = dns_record_validate(['type'=>$type,'name'=>$name,'content'=>$content,'ttl'=>$ttl,'priority'=>$priority]);
        if (!$v['ok']) { dns_json(['error' => $v['error']], 422); }

        if ($type === 'TXT') { $content = dns_record_quote_txt($content); }
        $newLine = dns_record_content_line($type, $content, $priority);

        // For edit, the client sends the original raw content line we're replacing.
        $oldLine = null;
        if ($action === 'edit_record') {
            $oldLine = trim($_POST['original_content'] ?? '');
            if ($oldLine === '') { dns_json(['error' => 'Missing original record reference'], 422); }
        }
        $ok = $pdns->upsertRecordInRRset($domain_name, $name, $type, $ttl, $oldLine, $newLine);
        if (!$ok) { dns_json(['error' => 'PowerDNS write failed'], 500); }
        dns_json(['success' => true]);
    }

    case 'delete_record': {
        $domain_name = trim($_POST['domain_name'] ?? '');
        dns_require_domain_access($db, $domain_name, $is_root);
        $type = strtoupper(trim($_POST['record_type'] ?? ''));
        $name = trim($_POST['record_name'] ?? '');
        $rawContent = trim($_POST['raw_content'] ?? '');
        $ttl = dns_record_normalize_ttl($_POST['ttl'] ?? 300);
        if ($type === '' || $rawContent === '') { dns_json(['error' => 'Missing record reference'], 422); }
        $ok = $pdns->deleteRecordInRRset($domain_name, $name, $type, $ttl, $rawContent);
        if (!$ok) { dns_json(['error' => 'PowerDNS delete failed'], 500); }
        dns_json(['success' => true]);
    }

    case 'bulk_delete':
    case 'bulk_ttl':
    case 'bulk_toggle': {
        $domain_name = trim($_POST['domain_name'] ?? '');
        dns_require_domain_access($db, $domain_name, $is_root);
        $records = json_decode($_POST['records'] ?? '[]', true);
        if (!is_array($records) || empty($records)) { dns_json(['error' => 'No records selected'], 422); }

        $newTtl = dns_record_normalize_ttl($_POST['ttl'] ?? 300);
        $disabled = ($_POST['disabled'] ?? '0') === '1';
        $done = 0; $failed = 0;
        foreach ($records as $rec) {
            $name = trim($rec['name'] ?? '');
            $type = strtoupper(trim($rec['type'] ?? ''));
            $raw  = trim($rec['raw_content'] ?? '');
            $ttl  = dns_record_normalize_ttl($rec['ttl'] ?? 300);
            if ($type === '' || $raw === '') { $failed++; continue; }
            if ($action === 'bulk_delete') {
                $ok = $pdns->deleteRecordInRRset($domain_name, $name, $type, $ttl, $raw);
            } else if ($action === 'bulk_ttl') {
                // Re-write the same line with the new TTL.
                $ok = $pdns->upsertRecordInRRset($domain_name, $name, $type, $newTtl, $raw, $raw);
            } else { // bulk_toggle
                $ok = $pdns->setRecordDisabled($domain_name, $name, $type, $ttl, $raw, $disabled);
            }
            $ok ? $done++ : $failed++;
        }
        if ($failed > 0 && $done === 0) { dns_json(['error' => "All $failed operations failed"], 500); }
        dns_json(['success' => true, 'done' => $done, 'failed' => $failed]);
    }

    case 'add_domain': {
        require_once('../libs/emailmgmt.php');

        if (empty($_POST['domain_name'])) {
            dns_json(['error' => 'Domain name is required'], 422);
        }

        // Set up Email Management for mail server synchronization (optional).
        try {
            $EmailMgmt = new emailmgmt();
        } catch (Exception $e) {
            $EmailMgmt = null;
        }

        try {
            $domain_name = strtolower(trim($_POST['domain_name']));

            // Validate domain format
            if (!filter_var('http://' . $domain_name, FILTER_VALIDATE_URL)) {
                dns_json(['error' => 'Invalid domain format'], 422);
            }

            // Check if domain already exists in WHP
            $stmt = $db->prepare("SELECT id FROM whp.domains WHERE domain_name = ?");
            $stmt->execute([$domain_name]);
            if ($stmt->fetch()) {
                dns_json(['error' => 'Domain already exists in the system'], 409);
            }

            // Get user ID
            $owner_username = $is_root && !empty($_POST['owner_username']) ? $_POST['owner_username'] : AUTH_USER;
            $stmt = $db->prepare("SELECT username FROM whp.client_allowances WHERE username = ?");
            $stmt->execute([$owner_username]);
            $user = $stmt->fetch();

            if (!$user) {
                dns_json(['error' => 'Invalid user specified'], 422);
            }

            // Create PowerDNS zone first
            $zone_result = $pdns->createZoneForDomain($domain_name, $owner_username);
            if (!$zone_result['success']) {
                dns_json(['error' => 'Failed to create DNS zone: ' . $zone_result['error']], 500);
            }

            // Insert domain into WHP database (no container type assigned - will be set when used in Sites)
            $stmt = $db->prepare("
                INSERT INTO whp.domains (domain_name, username, active)
                VALUES (?, ?, 1)
            ");
            $stmt->execute([$domain_name, $owner_username]);

            // Follow proper sequence: PowerDNS → Mail Server → DKIM Generation → DNS Record Creation
            $mail_sync_success = true;
            $mail_sync_message = '';
            $dkim_success = true;
            $dkim_message = '';
            $dns_success = true;
            $dns_message = '';

            if ($EmailMgmt) {
                try {
                    // Step 1: PowerDNS zone already created above

                    // Step 2: Add domain to mail server
                    $mail_result = $EmailMgmt->createMailServerDomain($domain_name, [
                        'disabled' => false,
                        'reference_id' => $owner_username
                    ]);
                    if ($mail_result['status'] !== '0') {
                        $mail_sync_success = false;
                        $mail_sync_message = $mail_result['msg'];
                    } else {
                        // Step 2.5: Wait for mail server to process domain creation
                        // This delay allows the mail server to fully process the domain before DKIM creation
                        sleep(3);

                        // Step 3: Generate DKIM records on mail server with retry logic
                        try {
                            $dkim_result = $EmailMgmt->createDkimRecordsWithRetry($domain_name, $owner_username, 3, 2);
                            if ($dkim_result['status'] !== '0') {
                                $dkim_success = false;
                                $dkim_message = $dkim_result['msg'];
                            } else {
                                // Step 4: Add DKIM DNS record to PowerDNS
                                $dns_result = $EmailMgmt->createDkimDnsRecord($domain_name, null, null, $owner_username);
                                if ($dns_result['status'] !== '0') {
                                    $dns_success = false;
                                    $dns_message = $dns_result['msg'];
                                }
                            }
                        } catch (Exception $e) {
                            $dkim_success = false;
                            $dkim_message = $e->getMessage();
                        }
                    }
                } catch (Exception $e) {
                    $mail_sync_success = false;
                    $mail_sync_message = $e->getMessage();
                }
            }

            // Determine success message based on all operations. The domain WAS
            // created even if mail/DKIM warned — collect warnings and append.
            $warnings = [];
            // Mutually-exclusive by design: DKIM/DNS are only attempted when the
                // prior step succeeded, so at most one warning ever applies (faithful
                // to the original page handler's precedence).
            if (!$mail_sync_success) {
                $warnings[] = 'Mail server sync failed: ' . $mail_sync_message;
            } else if (!$dkim_success) {
                $warnings[] = 'DKIM setup failed after retries: ' . $dkim_message;
            } else if (!$dns_success) {
                $warnings[] = 'DKIM DNS record creation failed: ' . $dns_message;
            }

            if (empty($warnings)) {
                $message = 'Domain added successfully: ' . $domain_name . ' (DNS zone, mail server, DKIM, and DNS records all configured)';
            } else {
                $message = 'Domain added successfully: ' . $domain_name . '. Warning: ' . implode('. ', $warnings);
            }
            dns_json(['success' => true, 'message' => $message]);
        } catch (PDOException $e) {
            dns_json(['error' => 'Error adding domain: ' . $e->getMessage()], 500);
        }
    }

    case 'delete_domain': {
        require_once('../libs/emailmgmt.php');

        if (empty($_POST['domain_id'])) {
            dns_json(['error' => 'Domain ID is required'], 422);
        }

        // Set up Email Management for mail server synchronization (optional).
        try {
            $EmailMgmt = new emailmgmt();
        } catch (Exception $e) {
            $EmailMgmt = null;
        }

        try {
            $domain_id = intval($_POST['domain_id']);

            // Get domain info
            $stmt = $db->prepare("
                SELECT d.*, d.username
                FROM whp.domains d
                WHERE d.id = ?
            ");
            $stmt->execute([$domain_id]);
            $domain = $stmt->fetch();

            if (!$domain) {
                dns_json(['error' => 'Domain not found'], 404);
            }

            // Check ownership (users can delete their own domains, root can delete any)
            if (!$is_root && $domain['username'] !== AUTH_USER) {
                dns_json(['error' => 'You can only delete your own domains'], 403);
            }

            // Check if domain has active containers
            $stmt = $db->prepare("SELECT COUNT(*) as count FROM whp.containers WHERE domain_id = ? AND status = 'running'");
            $stmt->execute([$domain_id]);
            $container_count = $stmt->fetch()['count'];

            if ($container_count > 0) {
                dns_json(['error' => 'Cannot delete domain with active containers. Stop containers first.'], 409);
            }

            // Check site attachments. JOIN against whp.sites so we
            // ignore orphan site_domains rows that somehow survived
            // a site delete (cascade FK should catch this; the
            // JOIN is defense-in-depth). Collect role info so we
            // can decide between hard-block (primary attachment)
            // and auto-detach (additional-only).
            $stmt = $db->prepare("
                SELECT s.id, s.site_name, s.active, sd.is_primary
                FROM whp.site_domains sd
                JOIN whp.sites s ON sd.site_id = s.id
                WHERE sd.domain_id = ?
                ORDER BY sd.is_primary DESC, s.id
            ");
            $stmt->execute([$domain_id]);
            $linked_sites = $stmt->fetchAll(PDO::FETCH_ASSOC);

            $primary_attachments = array_values(array_filter($linked_sites, fn($r) => (int)$r['is_primary'] === 1));
            $additional_attachments = array_values(array_filter($linked_sites, fn($r) => (int)$r['is_primary'] !== 1));

            if (!empty($primary_attachments)) {
                // Primary attachment blocks the delete — removing
                // the domain row would leave the site with no
                // primary_domain_id reference. Operator has to
                // change the primary or delete the site first.
                $site_descs = array_map(function ($r) {
                    $status = $r['active'] ? '' : ' (inactive)';
                    return '#' . $r['id'] . ' ' . $r['site_name'] . $status;
                }, array_slice($primary_attachments, 0, 5));
                dns_json(['error' => 'Cannot delete domain — it is the primary domain for: '
                         . implode('; ', $site_descs)
                         . '. Either delete the site itself or change the site\'s primary domain first.'], 409);
            }

            // Additional-only attachment: safe to auto-detach
            // because the domain is just an alias entry on the
            // site, not the site's primary identity. Drop the
            // site_domains rows BEFORE the domain delete so the
            // FK doesn't bite. Audit-log so the operator can
            // trace what was detached if they expected the alias
            // to stick around.
            if (!empty($additional_attachments)) {
                $stmt = $db->prepare("DELETE FROM whp.site_domains WHERE domain_id = ?");
                $stmt->execute([$domain_id]);
                $detached = (int) $stmt->rowCount();
                $site_ids = implode(',', array_column($additional_attachments, 'id'));
                error_log("domains: detached domain_id=$domain_id from $detached additional-domain attachment(s) [sites=$site_ids] before delete (actor=" . AUTH_USER . ")");
            }

            // Also sweep any truly-orphaned site_domains rows
            // referencing this domain_id (parent site missing).
            // FK cascade should make this a no-op but historical
            // drift on long-running installs could leave straggler
            // rows. Removing here means the domain delete below
            // succeeds cleanly.
            $stmt = $db->prepare("
                DELETE FROM whp.site_domains
                WHERE domain_id = ?
                  AND site_id NOT IN (SELECT id FROM whp.sites)
            ");
            $stmt->execute([$domain_id]);
            $cleaned = (int) $stmt->rowCount();
            if ($cleaned > 0) {
                error_log("domains: cleaned $cleaned orphan site_domains row(s) for domain_id=$domain_id before delete (actor=" . AUTH_USER . ")");
            }

            // Delete PowerDNS zone first
            $warnings = [];
            $zone_result = $pdns->deleteZoneForDomain($domain['domain_name'], $domain['username']);
            if (!$zone_result['success']) {
                $warnings[] = 'Failed to delete DNS zone: ' . $zone_result['error'];
            }

            // Delete domain from mail server
            $mail_sync_success = true;
            $mail_sync_message = '';
            if ($EmailMgmt) {
                try {
                    $mail_result = $EmailMgmt->deleteMailServerDomain($domain['domain_name']);
                    if ($mail_result['status'] !== '0') {
                        $mail_sync_success = false;
                        $mail_sync_message = $mail_result['msg'];
                    }
                } catch (Exception $e) {
                    $mail_sync_success = false;
                    $mail_sync_message = $e->getMessage();
                }
            }

            // Clean up email database entries for this domain
            $email_cleanup_errors = [];
            if ($EmailMgmt) {
                $cleanup_result = $EmailMgmt->cleanupDomainEmailEntries($domain['domain_name']);
                if ($cleanup_result['status'] !== '0') {
                    $email_cleanup_errors[] = $cleanup_result['msg'];
                }
            } else {
                $email_cleanup_errors[] = "Email management system not available for cleanup";
            }

            // Delete domain from WHP database
            $stmt = $db->prepare("DELETE FROM whp.domains WHERE id = ?");
            $stmt->execute([$domain_id]);

            if (!$mail_sync_success) {
                $warnings[] = 'Mail server removal failed: ' . $mail_sync_message;
            }
            if (!empty($email_cleanup_errors)) {
                $warnings[] = implode(', ', $email_cleanup_errors);
            }

            if (empty($warnings)) {
                $message = 'Domain deleted successfully: ' . $domain['domain_name'] . ' (removed from mail server and email database entries cleaned up)';
            } else {
                $message = 'Domain deleted: ' . $domain['domain_name'] . '. Warning: ' . implode('. ', $warnings);
            }
            dns_json(['success' => true, 'message' => $message]);
        } catch (PDOException $e) {
            dns_json(['error' => 'Error deleting domain: ' . $e->getMessage()], 500);
        }
    }

    case 'move_domain': {
        if (!$is_root) {
            dns_json(['error' => 'Only root can move domains'], 403);
        }

        if (empty($_POST['domain_id']) || empty($_POST['new_owner'])) {
            dns_json(['error' => 'Domain ID and new owner are required'], 422);
        }

        try {
            $domain_id = intval($_POST['domain_id']);
            $new_owner = trim($_POST['new_owner']);

            // Get domain info
            $stmt = $db->prepare("SELECT domain_name, username FROM whp.domains WHERE id = ?");
            $stmt->execute([$domain_id]);
            $domain = $stmt->fetch();

            if (!$domain) {
                dns_json(['error' => 'Domain not found'], 404);
            }

            // Check if new owner exists
            $stmt = $db->prepare("SELECT username FROM whp.client_allowances WHERE username = ?");
            $stmt->execute([$new_owner]);
            $new_user = $stmt->fetch();

            if (!$new_user) {
                dns_json(['error' => 'Invalid new owner specified'], 422);
            }

            // Check if domain is used by any active sites
            $stmt = $db->prepare("SELECT COUNT(*) as count FROM whp.site_domains WHERE domain_id = ?");
            $stmt->execute([$domain_id]);
            $site_usage = $stmt->fetch()['count'];

            if ($site_usage > 0) {
                dns_json(['error' => 'Cannot move domain that is used by active sites. Remove from sites first.'], 409);
            }

            // Update domain owner
            $stmt = $db->prepare("UPDATE whp.domains SET username = ? WHERE id = ?");
            $stmt->execute([$new_owner, $domain_id]);

            $message = 'Domain moved successfully: ' . $domain['domain_name'] . ' → ' . $new_owner;
            dns_json(['success' => true, 'message' => $message]);
        } catch (PDOException $e) {
            dns_json(['error' => 'Error moving domain: ' . $e->getMessage()], 500);
        }
    }

    default:
        dns_json(['error' => 'Unknown action: ' . $action], 400);
}
