<?php

namespace App\Services\Whmcs;

use App\Models\Ticket;
use App\Models\WhmcsServer;

/**
 * Gate that says: "is this ticket's customer authorised to see data
 * about THIS domain / server?". Required before any drafter / admin-
 * note path quotes server-level data — without this, a malicious
 * ticket could ask "is example.com working?" and the AI would pull
 * data about a domain the customer doesn't own. Cross-tenant data
 * leak by way of a confused-deputy AI.
 *
 * Returns the ALLOWED set of (domain, whmcs_server_id) tuples for the
 * ticket, derived from the customer's actual WHMCS products. Anything
 * outside this set is OFF-LIMITS regardless of what the AI infers
 * from the ticket body.
 *
 * Reads from the local whmcs_client_products mirror (refreshed every
 * 6h, lazy on miss), so the verification is O(1) DB lookup — fast
 * enough to call on every draft.
 */
class TicketOwnershipVerifier
{
    public function __construct(private WhmcsMirrorSyncer $mirror) {}

    /**
     * Returns the customer's allowed scope:
     *   [
     *     'domains' => ['example.com', 'other-site.org', ...],
     *     'whmcs_server_ids' => [5, 9, ...],
     *     'products' => [<product rows>],
     *   ]
     * Empty arrays if the ticket has no WHMCS user link OR if the
     * customer simply has no active products.
     */
    public function scopeForTicket(Ticket $ticket): array
    {
        $empty = ['domains' => [], 'whmcs_server_ids' => [], 'products' => []];
        if (! $ticket->whmcs_user_id) return $empty;

        $products = $this->mirror->clientProducts((int) $ticket->whmcs_user_id);
        if (empty($products)) return $empty;

        $domains = [];
        $serverIds = [];
        foreach ($products as $p) {
            $d = strtolower(trim((string) ($p['domain'] ?? '')));
            if ($d !== '') $domains[$d] = true;
            $sid = (int) ($p['serverid'] ?? 0);
            if ($sid > 0) $serverIds[$sid] = true;
        }

        return [
            'domains'          => array_keys($domains),
            'whmcs_server_ids' => array_keys($serverIds),
            'products'         => $products,
        ];
    }

    /**
     * Is the given domain inside the customer's scope?
     * Strict: exact match only. We don't fuzzy-match because
     * "example.com" and "example.com.evil.tld" are different domains
     * and treating them as the same is exactly the cross-tenant bug
     * we're trying to avoid.
     */
    public function ownsDomain(Ticket $ticket, string $domain): bool
    {
        $scope = $this->scopeForTicket($ticket);
        $needle = strtolower(trim($domain));
        return in_array($needle, $scope['domains'], true);
    }

    /**
     * Is the given WHMCS server id inside the customer's scope?
     * The drafter / admin-note jobs call this BEFORE quoting any
     * server-level data (disk, mail-queue, etc.) — the customer must
     * have at least one product on that server.
     */
    public function ownsServer(Ticket $ticket, int $whmcsServerId): bool
    {
        if ($whmcsServerId <= 0) return false;
        $scope = $this->scopeForTicket($ticket);
        return in_array($whmcsServerId, $scope['whmcs_server_ids'], true);
    }

    /**
     * Convenience: resolve the local Server row(s) for the customer's
     * services. Used by RecentMetricsContext to pull only authorised
     * agent data. Matches WhmcsServer rows by `whmcs_id`, then NOC's
     * `servers` table by hostname (same join pattern ServerHealthContext
     * uses).
     *
     * @return \App\Models\Server[]  list of NOC servers the customer is on
     */
    public function nocServersForTicket(Ticket $ticket): array
    {
        $scope = $this->scopeForTicket($ticket);
        if (empty($scope['whmcs_server_ids'])) return [];

        // WHMCS server → local mirror → hostname.
        $hostnames = WhmcsServer::whereIn('whmcs_id', $scope['whmcs_server_ids'])
            ->pluck('hostname')
            ->filter()
            ->map(fn ($h) => strtolower($h))
            ->all();
        if (empty($hostnames)) return [];

        // Then NOC's own `servers` table — exact OR dotless-prefix match
        // (e.g. WHMCS "alpha.hostseba.com" ↔ NOC "alpha"). Same algorithm
        // as ServerHealthContext for consistency.
        return \App\Models\Server::query()
            ->where(function ($q) use ($hostnames) {
                foreach ($hostnames as $h) {
                    $q->orWhere('hostname', $h);
                    if (str_contains($h, '.')) {
                        $q->orWhere('hostname', explode('.', $h, 2)[0]);
                    }
                }
            })
            ->get()
            ->all();
    }
}
