<?php
// /lib/sso_settings.php
// Central helpers for SSO configuration.
//
// Table: sso_settings
// Columns:
//   id, enabled, provider, tenant_id, client_id, client_secret,
//   redirect_uri, allowed_domains, updated_at
//
// Usage:
//   - For multi-tenant Entra ID:
//       tenant_id = 'common' (recommended) or leave blank (we treat as 'common').
//   - For single-tenant:
//       tenant_id = '{your-tenant-guid}'.
//   - allowed_domains:
//       optional JSON array OR CSV string of allowed domains.
//
// Also includes per-company SSO helpers using company_sso_settings.

require_once __DIR__ . '/db.php';

/**
 * Normalise SSO settings structure loaded from DB or config.
 *
 * @param array $s
 * @return array
 */
function normalize_sso_settings(array $s): array
{
    $s['enabled']   = !empty($s['enabled']);
    $s['provider']  = $s['provider'] ?? 'entra';

    $tenant = trim((string)($s['tenant_id'] ?? ''));
    // If empty, default to multi-tenant /common
    if ($tenant === '') {
        $tenant = 'common';
    }
    $s['tenant_id'] = $tenant;

    $s['client_id']     = trim((string)($s['client_id']     ?? ''));
    $s['client_secret'] = (string)($s['client_secret']     ?? '');
    $s['redirect_uri']  = trim((string)($s['redirect_uri']  ?? ''));

    // allowed_domains may be JSON or CSV
    if (array_key_exists('allowed_domains', $s) && $s['allowed_domains'] !== null && $s['allowed_domains'] !== '') {
        $raw = $s['allowed_domains'];

        if (is_string($raw)) {
            $decoded = json_decode($raw, true);
            if (is_array($decoded)) {
                $domains = $decoded;
            } else {
                // Assume CSV
                $domains = array_map('trim', explode(',', $raw));
            }
        } elseif (is_array($raw)) {
            $domains = $raw;
        } else {
            $domains = [];
        }

        $domains = array_values(array_filter(array_map('strtolower', $domains)));
        $s['allowed_domains'] = $domains ?: null;
    } else {
        $s['allowed_domains'] = null;
    }

    return $s;
}

/**
 * Load global SSO settings (backwards compatible with your existing usage).
 *
 * Source priority:
 *  1) DB: sso_settings WHERE id=1
 *  2) config['sso'] (legacy fallback)
 *
 * Returns:
 *  [
 *    enabled         => bool,
 *    provider        => 'entra',
 *    tenant_id       => 'common' | '{tenant-guid}',
 *    client_id       => string,
 *    client_secret   => string,
 *    redirect_uri    => string,
 *    allowed_domains => string[]|null
 *  ]
 *
 * @param array $config
 * @param int   $ttl
 * @return array
 */
function load_sso_settings(array $config, int $ttl = 60): array
{
    static $memo = null;
    if ($memo !== null) {
        return $memo;
    }

    $cacheFile = sys_get_temp_dir() . '/mpms_sso_settings.json';
    $now       = time();

    // 1) Cache
    if (is_file($cacheFile) && ($now - filemtime($cacheFile) < $ttl)) {
        $data = json_decode((string)file_get_contents($cacheFile), true);
        if (is_array($data)) {
            return $memo = normalize_sso_settings($data);
        }
    }

    // 2) DB (preferred)
    try {
        global $pdo;
        if ($pdo instanceof PDO) {
            $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

            $stmt = $pdo->query("SELECT * FROM sso_settings WHERE id = 1 LIMIT 1");
            $row  = $stmt->fetch(PDO::FETCH_ASSOC);

            if ($row) {
                $settings = [
                    'enabled'         => (bool)($row['enabled'] ?? 0),
                    'provider'        => $row['provider'] ?? 'entra',
                    'tenant_id'       => $row['tenant_id'] ?? '',
                    'client_id'       => $row['client_id'] ?? '',
                    'client_secret'   => $row['client_secret'] ?? '',
                    'redirect_uri'    => $row['redirect_uri'] ?? '',
                    'allowed_domains' => $row['allowed_domains'] ?? null,
                ];

                $settings = normalize_sso_settings($settings);
                @file_put_contents($cacheFile, json_encode($settings, JSON_UNESCAPED_SLASHES));

                return $memo = $settings;
            }
        }
    } catch (Throwable $e) {
        // Ignore and fall back
    }

    // 3) Fallback to config['sso'] if present
    $fallback = $config['sso'] ?? [];

    $settings = [
        'enabled'         => !empty($fallback['enabled']),
        'provider'        => $fallback['provider']    ?? 'entra',
        'tenant_id'       => $fallback['tenant_id']   ?? '',
        'client_id'       => $fallback['client_id']   ?? '',
        'client_secret'   => $fallback['client_secret'] ?? '',
        'redirect_uri'    => $fallback['redirect_uri']  ?? '',
        'allowed_domains' => $fallback['allowed_domains'] ?? null,
    ];

    $settings = normalize_sso_settings($settings);
    @file_put_contents($cacheFile, json_encode($settings, JSON_UNESCAPED_SLASHES));

    return $memo = $settings;
}

/* -----------------------------------------------------------------------
 * Per-company SSO mapping
 * ---------------------------------------------------------------------*/

/**
 * Ensure per-company SSO settings table exists.
 */
function ensure_company_sso_table(PDO $pdo): void
{
    $pdo->exec("
        CREATE TABLE IF NOT EXISTS company_sso_settings (
          id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
          organisation_id INT UNSIGNED NOT NULL,
          company_id      INT UNSIGNED NOT NULL,
          enabled         TINYINT(1) NOT NULL DEFAULT 1,
          tenant_id       VARCHAR(64) DEFAULT NULL,
          allowed_domains TEXT DEFAULT NULL,
          require_sso            TINYINT(1) NOT NULL DEFAULT 0,
          allow_password_fallback TINYINT(1) NOT NULL DEFAULT 1,
          created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
          updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
                     ON UPDATE CURRENT_TIMESTAMP,
          UNIQUE KEY uq_org_company (organisation_id, company_id),
          KEY idx_tenant (tenant_id),
          CONSTRAINT fk_css_org FOREIGN KEY (organisation_id)
            REFERENCES organisations(id) ON DELETE CASCADE,
          CONSTRAINT fk_css_company FOREIGN KEY (company_id)
            REFERENCES companies(id) ON DELETE CASCADE
        ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
    ");
}

/**
 * Any company SSO-enabled?
 */
function has_any_sso_enabled(PDO $pdo): bool
{
    try {
        $stmt = $pdo->query("SELECT 1 FROM company_sso_settings WHERE enabled = 1 LIMIT 1");
        return (bool)$stmt->fetchColumn();
    } catch (Throwable $e) {
        return false;
    }
}

/**
 * Map Entra claims -> company_sso_settings row via tenant_id or email domain.
 *
 * @return array|null
 */
function map_sso_claims_to_tenant(PDO $pdo, string $tenantId, string $email): ?array
{
    $email  = strtolower(trim($email));
    $domain = '';
    if (strpos($email, '@') !== false) {
        $domain = substr(strrchr($email, '@'), 1);
    }

    // 1) Exact tenant_id match
    if ($tenantId !== '') {
        $sql = "
            SELECT css.*,
                   o.name AS org_name, o.slug AS org_slug,
                   c.name AS company_name, c.slug AS company_slug
            FROM company_sso_settings css
            JOIN organisations o ON o.id = css.organisation_id
            JOIN companies      c ON c.id = css.company_id
            WHERE css.enabled = 1
              AND css.tenant_id = ?
            LIMIT 1
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$tenantId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row) {
            return $row;
        }
    }

    // 2) Fallback: match by allowed_domains CSV
    if ($domain !== '') {
        $sql = "
            SELECT css.*,
                   o.name AS org_name, o.slug AS org_slug,
                   c.name AS company_name, c.slug AS company_slug
            FROM company_sso_settings css
            JOIN organisations o ON o.id = css.organisation_id
            JOIN companies      c ON c.id = css.company_id
            WHERE css.enabled = 1
              AND (
                    css.allowed_domains IS NULL
                 OR css.allowed_domains = ''
                 OR FIND_IN_SET(?, REPLACE(css.allowed_domains, ' ', '')) > 0
              )
            LIMIT 1
        ";
        $stmt = $pdo->prepare($sql);
        $stmt->execute([$domain]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if ($row) {
            return $row;
        }
    }

    return null;
}

/**
 * Check if a given (org, company) requires SSO.
 */
function company_requires_sso(PDO $pdo, int $orgId, int $companyId): bool
{
    try {
        $stmt = $pdo->prepare("
            SELECT require_sso
            FROM company_sso_settings
            WHERE organisation_id = ? AND company_id = ?
            LIMIT 1
        ");
        $stmt->execute([$orgId, $companyId]);
        return (bool)$stmt->fetchColumn();
    } catch (Throwable $e) {
        return false;
    }
}

/**
 * Check if a given (org, company) allows password fallback.
 * Default: true (don’t lock people out if no row).
 */
function company_allows_password_fallback(PDO $pdo, int $orgId, int $companyId): bool
{
    try {
        $stmt = $pdo->prepare("
            SELECT allow_password_fallback
            FROM company_sso_settings
            WHERE organisation_id = ? AND company_id = ?
            LIMIT 1
        ");
        $stmt->execute([$orgId, $companyId]);
        $val = $stmt->fetchColumn();
        return ($val === false) ? true : (bool)$val;
    } catch (Throwable $e) {
        return true;
    }
}
