<?php
declare(strict_types=1);

/**
 * CorpusProvision — idempotent per-SSO-user tenant provisioning for /dashboard/.
 *
 * The dashboard treats every SSO user as their own CaveauAI client tenant so
 * that the existing client_id-based row filtering is enough to keep one user's
 * documents from leaking to another. Provisioning is lazy: the first time the
 * SSO user hits the dashboard, we create:
 *
 *   - one row in `clients`     (slug `dbn-user-<sso_uid>`, plan `sandbox`,
 *                               dbn_sso_uid = sso_uid, fresh api_key hash)
 *   - one row in `client_users` (role owner, random password hash — login is
 *                                always via SSO bridge, never password)
 *   - one row in `client_corpora` (slug `default`, is_default=1)
 *
 * CaveauAI sessions (which already have client_id + client_user_id set on the
 * tools session) are returned as-is without touching the DB.
 *
 * Output shape:
 *   [
 *     'client_id'      => int,
 *     'client_user_id' => int,
 *     'corpus_id'      => int,
 *     'created'        => bool,   // true the first time, false on every call after
 *   ]
 */
final class CorpusProvision
{
    public static function ensureForSsoUser(int $ssoUid, string $email, string $displayName): array
    {
        if ($ssoUid <= 0) {
            throw new DbnToolsHttpException('SSO user id is required.', 400, 'missing_sso_uid');
        }
        if ($email === '' || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
            throw new DbnToolsHttpException('A valid email is required.', 400, 'invalid_email');
        }

        dbnToolsBootCaveau();
        $db = getDb();

        $existing = self::lookupBySso($db, $ssoUid);
        if ($existing !== null) {
            $corpusId = self::ensureDefaultCorpus($db, $existing['client_id'], $email);
            return [
                'client_id'      => $existing['client_id'],
                'client_user_id' => $existing['client_user_id'],
                'corpus_id'      => $corpusId,
                'created'        => false,
            ];
        }

        $db->beginTransaction();
        try {
            $clientId      = self::createClient($db, $ssoUid, $email, $displayName);
            $clientUserId  = self::createOwnerUser($db, $clientId, $email, $displayName);
            $corpusId      = self::ensureDefaultCorpus($db, $clientId, $email);
            self::subscribeIncludedPackages($db, $clientId);
            $db->commit();
        } catch (Throwable $e) {
            $db->rollBack();
            throw new DbnToolsHttpException(
                'Could not provision dashboard tenant: ' . $e->getMessage(),
                500,
                'provision_failed'
            );
        }

        return [
            'client_id'      => $clientId,
            'client_user_id' => $clientUserId,
            'corpus_id'      => $corpusId,
            'created'        => true,
        ];
    }

    public static function ensureForCaveauSession(int $clientId, string $email): array
    {
        if ($clientId <= 0) {
            throw new DbnToolsHttpException('Caveau client_id is required.', 400, 'missing_client_id');
        }
        dbnToolsBootCaveau();
        $db = getDb();
        $corpusId = self::ensureDefaultCorpus($db, $clientId, $email);
        return [
            'client_id'      => $clientId,
            'client_user_id' => 0,
            'corpus_id'      => $corpusId,
            'created'        => false,
        ];
    }

    private static function lookupBySso(PDO $db, int $ssoUid): ?array
    {
        $stmt = $db->prepare('SELECT id FROM clients WHERE dbn_sso_uid = ? LIMIT 1');
        $stmt->execute([$ssoUid]);
        $clientId = (int)($stmt->fetchColumn() ?: 0);
        if ($clientId === 0) {
            return null;
        }
        $stmt = $db->prepare(
            "SELECT id FROM client_users
             WHERE client_id = ? AND role = 'owner' AND is_active = 1
             ORDER BY id ASC LIMIT 1"
        );
        $stmt->execute([$clientId]);
        $userId = (int)($stmt->fetchColumn() ?: 0);
        return ['client_id' => $clientId, 'client_user_id' => $userId];
    }

    private static function createClient(PDO $db, int $ssoUid, string $email, string $displayName): int
    {
        require_once dbnToolsAiPortalRoot() . '/platform/includes/client_auth.php';
        $apiKey = generateApiKey();

        $slug = self::uniqueSlug($db, 'dbn-user-' . $ssoUid);
        $name = $displayName !== '' ? $displayName : ('Dashboard ' . $email);

        $stmt = $db->prepare("
            INSERT INTO clients (
                dbn_sso_uid, slug, name, contact_email, country, timezone,
                plan, embedding_tier,
                api_key_hash, api_key_prefix,
                monthly_query_limit, monthly_document_limit, monthly_user_limit,
                is_active, plan_source, subscription_status
            ) VALUES (
                ?, ?, ?, ?, 'NO', 'Europe/Oslo',
                'sandbox', 'standard',
                ?, ?,
                500, 50, 1,
                1, 'signup', 'none'
            )
        ");
        $stmt->execute([
            $ssoUid,
            $slug,
            $name,
            $email,
            $apiKey['hash'],
            $apiKey['prefix'],
        ]);
        return (int)$db->lastInsertId();
    }

    private static function createOwnerUser(PDO $db, int $clientId, string $email, string $displayName): int
    {
        // client_users.email has a UNIQUE constraint, so an SSO-provisioned workspace
        // uses a synthetic internal email to avoid conflicting with any real CaveauAI
        // account that may share the same address. The real email lives on clients.contact_email.
        $ssoEmail = self::ssoInternalEmail($db, $clientId);

        $stmt = $db->prepare('SELECT id FROM client_users WHERE email = ? AND client_id = ? LIMIT 1');
        $stmt->execute([$ssoEmail, $clientId]);
        $existingId = (int)($stmt->fetchColumn() ?: 0);
        if ($existingId > 0) {
            return $existingId;
        }

        $usernameBase = preg_replace('/[^a-z0-9._-]+/', '', strtolower(strstr($email, '@', true) ?: 'owner'));
        $usernameBase = $usernameBase !== '' ? $usernameBase : 'owner';
        $username     = self::uniqueUsername($db, $usernameBase);

        $stmt = $db->prepare("
            INSERT INTO client_users
                (client_id, username, email, display_name, password_hash, role, is_active)
            VALUES (?, ?, ?, ?, ?, 'owner', 1)
        ");
        $stmt->execute([
            $clientId,
            $username,
            $ssoEmail,
            $displayName !== '' ? $displayName : null,
            password_hash(bin2hex(random_bytes(16)), PASSWORD_DEFAULT),
        ]);
        return (int)$db->lastInsertId();
    }

    private static function ssoInternalEmail(PDO $db, int $clientId): string
    {
        // Derive a stable internal-only email from the client_id so it is unique
        // even when the same real address already exists for another workspace.
        return 'dbn-sso-' . $clientId . '@dbn.tools.internal';
    }

    private static function ensureDefaultCorpus(PDO $db, int $clientId, string $email): int
    {
        $stmt = $db->prepare(
            'SELECT id FROM client_corpora
             WHERE client_id = ? AND is_default = 1
             ORDER BY id ASC LIMIT 1'
        );
        $stmt->execute([$clientId]);
        $id = (int)($stmt->fetchColumn() ?: 0);
        if ($id > 0) {
            return $id;
        }

        $stmt = $db->prepare("
            INSERT INTO client_corpora (client_id, name, slug, description, is_default)
            VALUES (?, ?, 'default', ?, 1)
        ");
        $stmt->execute([
            $clientId,
            'Min korpus',
            'Default personal corpus for ' . $email,
        ]);
        return (int)$db->lastInsertId();
    }

    private static function subscribeIncludedPackages(PDO $db, int $clientId): void
    {
        $packageSlug = dbnToolsRequiredPackageSlug();
        $stmt = $db->prepare('SELECT id FROM corpus_packages WHERE slug = ? AND is_active = 1 LIMIT 1');
        $stmt->execute([$packageSlug]);
        $packageId = (int)($stmt->fetchColumn() ?: 0);
        if ($packageId === 0) {
            return;
        }
        $db->prepare(
            "INSERT IGNORE INTO client_corpus_subscriptions
                (client_id, package_id, is_active, source, subscribed_at)
             VALUES (?, ?, 1, 'dbn_dashboard', NOW())"
        )->execute([$clientId, $packageId]);
    }

    private static function uniqueSlug(PDO $db, string $base): string
    {
        $base = preg_replace('/[^a-z0-9-]+/', '-', strtolower($base)) ?: 'dbn-user';
        $base = trim($base, '-');
        $stmt = $db->prepare('SELECT id FROM clients WHERE slug = ? LIMIT 1');
        $slug = $base;
        $suffix = 2;
        while (true) {
            $stmt->execute([$slug]);
            if (!$stmt->fetch(PDO::FETCH_ASSOC)) {
                return $slug;
            }
            $slug = $base . '-' . $suffix;
            $suffix++;
        }
    }

    private static function uniqueUsername(PDO $db, string $base): string
    {
        $stmt = $db->prepare('SELECT id FROM client_users WHERE username = ? LIMIT 1');
        $username = $base;
        $suffix = 2;
        while (true) {
            $stmt->execute([$username]);
            if (!$stmt->fetch(PDO::FETCH_ASSOC)) {
                return $username;
            }
            $username = $base . $suffix;
            $suffix++;
        }
    }
}
