<?php
/**
 * TowLens SCIM Users endpoint (multi-tenant, company-scoped)
 *
 * URL patterns (via .htaccess):
 *   /scim/v2/Users
 *   /scim/v2/Users/{id}
 *
 * REQUIREMENTS
 * ------------
 * Table: company_sso_configs
 *   id, company_id, entra_tenant_id, entra_client_id, entra_client_secret,
 *   redirect_uri, scim_bearer_token, scim_enabled
 *
 * Table: users  (your existing users table, with at least:)
 *   id INT PK
 *   organisation_id INT NULL
 *   company_id INT NULL
 *   email VARCHAR(...)
 *   display_name VARCHAR(...)
 *   provider ENUM('local','entra',...)
 *   oid VARCHAR(64) NULL
 *   scim_external_id VARCHAR(128) NULL
 *   is_active TINYINT(1)
 *   created_at DATETIME NULL
 *
 * RECOMMENDED INDEXES
 * -------------------
 *   ALTER TABLE users
 *     ADD INDEX idx_company (company_id),
 *     ADD INDEX idx_scim_external (company_id, scim_external_id),
 *     ADD INDEX idx_email (company_id, email);
 *
 * SECURITY MODEL
 * --------------
 * - Azure AD (Entra) sends: Authorization: Bearer <scim_bearer_token>
 * - We look up that token in company_sso_configs:
 *       scim_bearer_token -> company_id
 * - All operations in this request are then forced to that company_id.
 * - No company_id is accepted from Entra mappings (prevents tenant spoofing).
 */

declare(strict_types=1);

header('Content-Type: application/json; charset=utf-8');

// Bootstrap TowLens app
$root       = dirname(__DIR__); // /.../towlens
$configFile = $root . '/lib/config.php';
$dbFile     = $root . '/lib/db.php';

if (!is_file($configFile) || !is_file($dbFile)) {
    http_response_code(500);
    echo json_encode([
        'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
        'detail'  => 'Config/DB bootstrap missing.',
        'status'  => '500',
    ]);
    exit;
}

$config = require $configFile;
require $dbFile;

if (!($pdo instanceof PDO)) {
    http_response_code(500);
    echo json_encode([
        'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
        'detail'  => 'Database connection not available.',
        'status'  => '500',
    ]);
    exit;
}

$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

/* ========================================================================== */
/* Helpers                                                                    */
/* ========================================================================== */

function scim_error(int $status, string $detail, ?string $scimType = null): void
{
    http_response_code($status);
    $err = [
        'schemas' => ['urn:ietf:params:scim:api:messages:2.0:Error'],
        'detail'  => $detail,
        'status'  => (string)$status,
    ];
    if ($scimType !== null) {
        $err['scimType'] = $scimType;
    }
    echo json_encode($err);
    exit;
}

function get_json_body(): array
{
    $raw = file_get_contents('php://input');
    if ($raw === '' || $raw === false) {
        return [];
    }
    $data = json_decode($raw, true);
    if (!is_array($data)) {
        scim_error(400, 'Invalid JSON body', 'invalidSyntax');
    }
    return $data;
}

/**
 * Resolve company_id from SCIM bearer token.
 * Supports various server setups (nginx+Apache, lsapi, etc.).
 */
function resolve_company_id_from_token(PDO $pdo): int
{
    $authHeader = '';

    // Common locations
    if (!empty($_SERVER['HTTP_AUTHORIZATION'])) {
        $authHeader = $_SERVER['HTTP_AUTHORIZATION'];
    } elseif (!empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) {
        $authHeader = $_SERVER['REDIRECT_HTTP_AUTHORIZATION'];
    }

    // Fallback via getallheaders()
    if ($authHeader === '' && function_exists('getallheaders')) {
        foreach (getallheaders() as $name => $value) {
            if (strtolower($name) === 'authorization') {
                $authHeader = $value;
                break;
            }
        }
    }

    $token = '';

    if ($authHeader !== '') {
        if (preg_match('/Bearer\s+(.+)/i', $authHeader, $m)) {
            $token = trim($m[1]);
        }
    }

    // Optional debug: allow ?access_token=
    if ($token === '' && isset($_GET['access_token'])) {
        $token = trim((string)$_GET['access_token']);
    }

    if ($token === '') {
        scim_error(401, 'Missing Bearer token');
    }

    $stmt = $pdo->prepare("
        SELECT company_id
        FROM company_sso_configs
        WHERE scim_bearer_token = ?
          AND scim_enabled = 1
        LIMIT 1
    ");
    $stmt->execute([$token]);
    $row = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$row) {
        scim_error(401, 'Invalid or disabled SCIM token');
    }

    $companyId = (int)$row['company_id'];
    if ($companyId <= 0) {
        scim_error(500, 'No company mapping for SCIM token');
    }

    return $companyId;
}

/**
 * Parse URL and return user ID segment if present.
 * Handles:
 *   /scim/v2/Users
 *   /scim/v2/Users/{id}
 * Ignores accidental /Users/Users.
 */
function get_scim_user_id_from_path(): ?string
{
    $uri   = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH);
    $parts = explode('/', trim((string)$uri, '/'));

    $idx = array_search('Users', $parts, true);
    if ($idx === false) {
        return null;
    }

    // No next part => collection
    if (!isset($parts[$idx + 1]) || $parts[$idx + 1] === '') {
        return null;
    }

    $candidate = $parts[$idx + 1];

    // If it's literally "Users" again, treat as collection
    if (strcasecmp($candidate, 'Users') === 0) {
        return null;
    }

    return $candidate;
}

/**
 * Build SCIM User resource from DB row.
 */
function build_scim_user(array $u): array
{
    $active = (int)($u['is_active'] ?? 1) === 1;
    $email  = (string)($u['email'] ?? '');
    $display = (string)($u['display_name'] ?? '');

    $given  = '';
    $family = '';

    if ($display !== '') {
        $parts = preg_split('/\s+/', $display);
        $given = $parts[0] ?? '';
        $family = $parts[1] ?? '';
    }

    $scim = [
        'schemas'    => ['urn:ietf:params:scim:schemas:core:2.0:User'],
        'id'         => (string)$u['id'],                      // local ID for PATCH/DELETE
        'externalId' => $u['scim_external_id'] ?? null,        // Entra objectId
        'userName'   => $email,
        'name'       => [
            'givenName'  => $given,
            'familyName' => $family,
        ],
        'active'     => $active,
        'emails'     => [],
        'meta'       => [
            'resourceType' => 'User',
        ],
    ];

    if ($email !== '') {
        $scim['emails'][] = [
            'value'   => $email,
            'primary' => true,
        ];
    }

    return $scim;
}

function send_scim_list(array $resources, int $startIndex, int $total): void
{
    $resp = [
        'schemas'      => ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
        'totalResults' => $total,
        'startIndex'   => $startIndex,
        'itemsPerPage' => count($resources),
        'Resources'    => $resources,
    ];
    echo json_encode($resp);
    exit;
}

/* ========================================================================== */
/* Main                                                                       */
/* ========================================================================== */

$method    = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$userIdStr = get_scim_user_id_from_path();
$companyId = resolve_company_id_from_token($pdo);

/* ============================ GET ================================== */

if ($method === 'GET') {

    // GET /Users/{id}
    if ($userIdStr !== null) {
        if (!ctype_digit($userIdStr)) {
            scim_error(400, 'User id must be numeric', 'invalidValue');
        }
        $uid = (int)$userIdStr;

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

        if (!$u) {
            scim_error(404, 'User not found', 'notFound');
        }

        echo json_encode(build_scim_user($u));
        exit;
    }

    // GET /Users (list)
    $startIndex = isset($_GET['startIndex']) ? max(1, (int)$_GET['startIndex']) : 1;
    $count      = isset($_GET['count'])      ? max(1, min(100, (int)$_GET['count'])) : 50;

    $where  = ["company_id = :company_id"];
    $params = [':company_id' => $companyId];

    // Minimal filter support: filter=userName eq "value"
    if (!empty($_GET['filter'])) {
        $filter = $_GET['filter'];
        if (preg_match('/userName\s+eq\s+"([^"]+)"/i', $filter, $m)) {
            $where[] = "email = :f_email";
            $params[':f_email'] = $m[1];
        }
    }

    $whereSql = 'WHERE ' . implode(' AND ', $where);

    // Count total
    $stmt = $pdo->prepare("SELECT COUNT(*) FROM users {$whereSql}");
    $stmt->execute($params);
    $total = (int)$stmt->fetchColumn();

    if ($total === 0) {
        send_scim_list([], $startIndex, 0);
    }

    $offset = $startIndex - 1;

    $sql = "SELECT * FROM users {$whereSql} ORDER BY id ASC LIMIT :limit OFFSET :offset";
    $stmt = $pdo->prepare($sql);
    foreach ($params as $k => $v) {
        $stmt->bindValue($k, $v);
    }
    $stmt->bindValue(':limit',  $count,   PDO::PARAM_INT);
    $stmt->bindValue(':offset', $offset,  PDO::PARAM_INT);
    $stmt->execute();

    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);
    $resources = array_map('build_scim_user', $rows);

    send_scim_list($resources, $startIndex, $total);
}

/* ============================ POST ================================= */
/* Create user */

if ($method === 'POST' && $userIdStr === null) {
    $body = get_json_body();

    $schemas = $body['schemas'] ?? [];
    if (!in_array('urn:ietf:params:scim:schemas:core:2.0:User', $schemas, true)) {
        scim_error(400, 'Missing User schema', 'invalidValue');
    }

    $userName   = trim((string)($body['userName'] ?? ''));
    $externalId = isset($body['externalId']) ? trim((string)$body['externalId']) : null;
    $active     = isset($body['active']) ? (bool)$body['active'] : true;

    // Prefer emails[0].value if present
    $email = $userName;
    if (!empty($body['emails'][0]['value'])) {
        $email = trim((string)$body['emails'][0]['value']);
    }

    if ($email === '') {
        scim_error(400, 'userName/email is required', 'invalidValue');
    }

    $given  = $body['name']['givenName']  ?? '';
    $family = $body['name']['familyName'] ?? '';
    $display = trim($given . ' ' . $family);
    if ($display === '') {
        $display = $email;
    }

    // Upsert-by-externalId within this company if provided
    if ($externalId !== null && $externalId !== '') {
        $stmt = $pdo->prepare("
            SELECT *
            FROM users
            WHERE company_id = ?
              AND scim_external_id = ?
            LIMIT 1
        ");
        $stmt->execute([$companyId, $externalId]);
        $existing = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($existing) {
            $stmt = $pdo->prepare("
                UPDATE users
                SET email = ?, display_name = ?, is_active = ?
                WHERE id = ? AND company_id = ?
            ");
            $stmt->execute([
                $email,
                $display,
                $active ? 1 : 0,
                $existing['id'],
                $companyId
            ]);

            $existing['email']        = $email;
            $existing['display_name'] = $display;
            $existing['is_active']    = $active ? 1 : 0;

            http_response_code(200);
            echo json_encode(build_scim_user($existing));
            exit;
        }
    }

    // Insert new SCIM user, hard-bound to this company
    $stmt = $pdo->prepare("
        INSERT INTO users (
            organisation_id,
            company_id,
            email,
            display_name,
            provider,
            oid,
            scim_external_id,
            is_active,
            created_at
        ) VALUES (
            :org_id,
            :company_id,
            :email,
            :display_name,
            'entra',
            :oid,
            :scim_external_id,
            :is_active,
            NOW()
        )
    ");

    $oid = $externalId ?: null;

    $stmt->execute([
        ':org_id'           => $companyId,       // keep org+company in sync for now
        ':company_id'       => $companyId,
        ':email'            => $email,
        ':display_name'     => $display,
        ':oid'              => $oid,
        ':scim_external_id' => $externalId,
        ':is_active'        => $active ? 1 : 0,
    ]);

    $id = (int)$pdo->lastInsertId();

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

    http_response_code(201);
    echo json_encode(build_scim_user($u));
    exit;
}

/* ============================ PATCH ================================ */
/* Update user */

if ($method === 'PATCH' && $userIdStr !== null) {
    if (!ctype_digit($userIdStr)) {
        scim_error(400, 'User id must be numeric', 'invalidValue');
    }
    $uid = (int)$userIdStr;

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

    if (!$u) {
        scim_error(404, 'User not found', 'notFound');
    }

    $body = get_json_body();
    $ops  = $body['Operations'] ?? [];
    if (!is_array($ops)) {
        scim_error(400, 'Invalid PATCH body', 'invalidSyntax');
    }

    $email        = $u['email'];
    $display      = $u['display_name'];
    $externalId   = $u['scim_external_id'];
    $active       = (int)$u['is_active'] === 1;

    foreach ($ops as $op) {
        $opType = strtoupper(trim($op['op'] ?? ''));
        $path   = isset($op['path']) ? trim($op['path']) : '';
        $value  = $op['value'] ?? null;

        if ($opType !== 'REPLACE') {
            continue; // minimal support
        }

        if ($path === '' && is_array($value)) {
            if (isset($value['active'])) {
                $active = (bool)$value['active'];
            }
            if (!empty($value['userName'])) {
                $email = trim((string)$value['userName']);
            }
            if (!empty($value['externalId'])) {
                $externalId = trim((string)$value['externalId']);
            }
            if (!empty($value['name']['givenName']) || !empty($value['name']['familyName'])) {
                $g = $value['name']['givenName']  ?? '';
                $f = $value['name']['familyName'] ?? '';
                $disp = trim($g . ' ' . $f);
                if ($disp !== '') {
                    $display = $disp;
                }
            }
            continue;
        }

        if (strcasecmp($path, 'active') === 0) {
            $active = (bool)$value;
        } elseif (strcasecmp($path, 'userName') === 0) {
            $email = trim((string)$value);
        } elseif (stripos($path, 'name.givenName') === 0 && is_string($value)) {
            $parts = preg_split('/\s+/', $display);
            $family = $parts[1] ?? '';
            $display = trim($value . ' ' . $family);
        } elseif (stripos($path, 'name.familyName') === 0 && is_string($value)) {
            $parts = preg_split('/\s+/', $display);
            $given = $parts[0] ?? '';
            $display = trim($given . ' ' . $value);
        }
    }

    $stmt = $pdo->prepare("
        UPDATE users
        SET email = ?, display_name = ?, scim_external_id = ?, is_active = ?
        WHERE id = ? AND company_id = ?
    ");
    $stmt->execute([
        $email,
        $display,
        $externalId,
        $active ? 1 : 0,
        $uid,
        $companyId
    ]);

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

    echo json_encode(build_scim_user($updated));
    exit;
}

/* ============================ DELETE =============================== */
/* Soft delete / deprovision */

if ($method === 'DELETE' && $userIdStr !== null) {
    if (!ctype_digit($userIdStr)) {
        scim_error(400, 'User id must be numeric', 'invalidValue');
    }
    $uid = (int)$userIdStr;

    $stmt = $pdo->prepare("
        SELECT id
        FROM users
        WHERE id = ?
          AND company_id = ?
        LIMIT 1
    ");
    $stmt->execute([$uid, $companyId]);
    $u = $stmt->fetch(PDO::FETCH_ASSOC);

    if (!$u) {
        scim_error(404, 'User not found', 'notFound');
    }

    $stmt = $pdo->prepare("
        UPDATE users
        SET is_active = 0
        WHERE id = ?
          AND company_id = ?
    ");
    $stmt->execute([$uid, $companyId]);

    // SCIM spec: 204 No Content
    http_response_code(204);
    exit;
}

/* ======================= Fallback: Method NG ======================= */

scim_error(405, 'Method not allowed', 'invalidSyntax');