<?php
// admin/sso/callback.php
//
// Handles Microsoft Entra ID (Azure AD) OIDC callback.
// Exchanges code for tokens, validates id_token, and logs user in.

session_start();

require __DIR__ . '/../../lib/config.php';
require __DIR__ . '/../../lib/db.php';
require __DIR__ . '/../../lib/sso.php';
require __DIR__ . '/../../lib/helpers.php'; // esc(), etc.

function sso_fail_cb(string $msg, ?array $debug = null): void
{
    http_response_code(500);
    ?>
    <!doctype html>
    <html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Single sign-on error</title>
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <style>
            body{
                font-family:system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
                background:#f3f4f6;color:#111827;margin:0;padding:40px;
                display:flex;justify-content:center
            }
            .box{
                max-width:620px;width:100%;background:#fff;border-radius:6px;
                padding:18px 20px;
                box-shadow:0 10px 30px rgba(15,23,42,.12);
            }
            h1{margin:0 0 8px;font-size:19px;color:#b91c1c}
            p{margin:4px 0 0;font-size:14px}
            pre{
                margin-top:10px;padding:8px;background:#f9fafb;border-radius:4px;
                font-size:11px;overflow:auto;color:#6b7280;max-height:260px
            }
        </style>
    </head>
    <body>
    <div class="box">
        <h1>Single sign-on error</h1>
        <p><?= htmlspecialchars($msg, ENT_QUOTES, 'UTF-8') ?></p>
        <?php if ($debug): ?>
            <pre><?= htmlspecialchars(json_encode($debug, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), ENT_QUOTES, 'UTF-8') ?></pre>
        <?php endif; ?>
    </div>
    </body>
    </html>
    <?php
    exit;
}

try {
    if (!$pdo instanceof PDO) {
        throw new RuntimeException('Database not available.');
    }
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

    // ---- Basic OIDC state checks ----

    if (!isset($_GET['code'])) {
        throw new RuntimeException('Missing authorization code.');
    }

    if (
        empty($_GET['state']) ||
        empty($_SESSION['sso_state']) ||
        !hash_equals($_SESSION['sso_state'], $_GET['state'])
    ) {
        throw new RuntimeException('Invalid SSO state.');
    }

    $code         = $_GET['code'];
    $codeVerifier = $_SESSION['sso_code_verifier'] ?? '';
    $nonceExpect  = $_SESSION['sso_nonce'] ?? '';
    $companyId    = (int)($_SESSION['sso_company_id'] ?? 0);

    if ($codeVerifier === '') {
        throw new RuntimeException('PKCE verifier missing from session.');
    }

    if (!function_exists('sso_build_config')) {
        throw new RuntimeException('SSO configuration helper missing.');
    }

    // Build SSO endpoints + client from DB config
    $cfg = sso_build_config($pdo, $companyId);

    // ---- Token request ----

    $token = http_post_form($cfg['token'], [
        'client_id'     => $cfg['client_id'],
        'client_secret' => $cfg['client_secret'],
        'grant_type'    => 'authorization_code',
        'code'          => $code,
        'redirect_uri'  => $cfg['redirect_uri'],
        'code_verifier' => $codeVerifier,
    ]);

    $idToken = $token['id_token'] ?? null;
    if (!$idToken) {
        throw new RuntimeException('No id_token returned by provider.');
    }

    // ---- Validate id_token ----

    $jwks   = load_jwks_cached($cfg['jwks_uri']);
    $claims = verify_jwt_rs256(
        $idToken,
        $jwks,
        $cfg['expected_issuer'], // '' means accept tenant from token
        $cfg['client_id']
    );

    // Nonce check
    if (!empty($nonceExpect)) {
        $nonceClaim = $claims['nonce'] ?? null;
        if (!$nonceClaim || !hash_equals($nonceExpect, $nonceClaim)) {
            throw new RuntimeException('Invalid nonce in ID token.');
        }
    }

    // ---- Extract user identity ----

    $email = strtolower(
        $claims['email']
        ?? $claims['preferred_username']
        ?? $claims['upn']
        ?? ''
    );

    if ($email === '' || strpos($email, '@') === false) {
        throw new RuntimeException('No valid email found in ID token.');
    }

    $emailDomain = strtolower(substr(strrchr($email, '@'), 1));

    // ---- Resolve company/org from company_sso_settings ----

    $orgId = null;

    if ($companyId > 0) {
        // We pinned a company in start.php
        $stmt = $pdo->prepare("
            SELECT css.*, c.organisation_id
            FROM company_sso_settings css
            JOIN companies c ON c.id = css.company_id
            WHERE css.company_id = ?
              AND css.enabled = 1
            LIMIT 1
        ");
        $stmt->execute([$companyId]);
        $css = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$css) {
            throw new RuntimeException('SSO configuration not found for selected company.');
        }

        $orgId = (int)$css['organisation_id'];

        $allowed = trim((string)($css['allowed_domains'] ?? ''));
        if ($allowed !== '') {
            if (strcasecmp($allowed, $emailDomain) !== 0) {
                throw new RuntimeException('Your account is not allowed for this organisation (email domain mismatch).');
            }
        }
    } else {
        // No pinned company: resolve by allowed_domains
        $stmt = $pdo->prepare("
            SELECT css.*, c.organisation_id
            FROM company_sso_settings css
            JOIN companies c ON c.id = css.company_id
            WHERE css.enabled = 1
              AND (
                    css.allowed_domains IS NULL
                 OR css.allowed_domains = ''
                 OR LOWER(css.allowed_domains) = ?
              )
            ORDER BY css.require_sso DESC
            LIMIT 1
        ");
        $stmt->execute([$emailDomain]);
        $css = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$css) {
            throw new RuntimeException('No SSO mapping found for your email domain.');
        }

        $companyId = (int)$css['company_id'];
        $orgId     = (int)$css['organisation_id'];
    }

    if ($companyId <= 0 || $orgId === null) {
        throw new RuntimeException('Unable to resolve tenant/company for this login.');
    }

    // ---- Find local user (must match resolved org/company) ----

    $stmt = $pdo->prepare("
        SELECT *
        FROM users
        WHERE email = ?
          AND company_id = ?
          AND organisation_id = ?
        LIMIT 1
    ");
    $stmt->execute([$email, $companyId, $orgId]);
    $user = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$user) {
        throw new RuntimeException('No matching user account found for ' . $email);
    }

    // ---- Normalize + store session user ----
    // Make this look exactly like the normal login session.

    // Fill missing org/company from SSO resolution, if needed
    if (empty($user['organisation_id'])) {
        $user['organisation_id'] = $orgId;
    }
    if (empty($user['company_id'])) {
        $user['company_id'] = $companyId;
    }

    // Strip sensitive fields if they exist
    unset(
        $user['password'],
        $user['password_hash'],
        $user['reset_token'],
        $user['reset_expires']
    );

    // Ensure integer types where your app expects them
    if (isset($user['id']))               $user['id']               = (int)$user['id'];
    if (isset($user['organisation_id']))  $user['organisation_id']  = (int)$user['organisation_id'];
    if (isset($user['company_id']))       $user['company_id']       = (int)$user['company_id'];

    // Leave role/is_admin/is_org_admin/etc EXACTLY as stored in DB.
    // This way require_admin_auth, index.php, view.php all behave identically.

    $_SESSION['user'] = $user;

    // Cleanup SSO temp values
    unset(
        $_SESSION['sso_code_verifier'],
        $_SESSION['sso_state'],
        $_SESSION['sso_nonce'],
        $_SESSION['sso_company_id']
    );
    
    // After fetching $user from DB

if (empty($user['organisation_id'])) {
    $user['organisation_id'] = $orgId;
}
if (empty($user['company_id'])) {
    $user['company_id'] = $companyId;
}

// Strip sensitive fields if present
unset(
    $user['password'],
    $user['password_hash'],
    $user['reset_token'],
    $user['reset_expires']
);

// Normalise key types
if (isset($user['id']))              $user['id']              = (int)$user['id'];
if (isset($user['organisation_id'])) $user['organisation_id'] = (int)$user['organisation_id'];
if (isset($user['company_id']))      $user['company_id']      = (int)$user['company_id'];

// IMPORTANT: do NOT touch $user['role'], $user['is_org_admin'], $user['is_admin'], etc.
$_SESSION['user'] = $user;

    // ---- Redirect to admin home ----

    $base = rtrim($config['base_url'] ?? '', '/');
    $dest = ($base ?: '') . '/admin/';
    header('Location: ' . $dest);
    exit;

} catch (Throwable $e) {
    $debug = [];
    if (!empty($GLOBALS['__mpms_last_http_body'])) {
        $debug['token_response'] = $GLOBALS['__mpms_last_http_body'];
    }
    sso_fail_cb($e->getMessage(), $debug);
}


