<?php
/**
 * TowLens single-file SCIM 2.0 endpoint
 *
 * Tenant URL (in Entra): https://client.towlens.net/scim.php
 *
 * Entra will call:
 *   GET  scim.php/ServiceProviderConfig
 *   GET  scim.php/Schemas
 *   GET  scim.php/Users
 *   GET  scim.php/Users/{id}
 *   POST scim.php/Users
 *   PATCH scim.php/Users/{id}
 *   DELETE scim.php/Users/{id}
 *
 * All calls are authenticated with:
 *   Authorization: Bearer <scim_bearer_token>
 *
 * We:
 *   - Map that token via company_sso_configs to company_id
 *   - Scope all user CRUD to that company_id
 *   - NEVER trust company_id from Entra
 */

declare(strict_types=1);

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

/* ============================================================================
 * Bootstrap TowLens
 * ========================================================================== */

$root       = __DIR__; // assume TowLens lives here; adjust if needed
$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;
}

function get_auth_token(): string
{
    $authHeader = '';

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

    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]);
        }
    }

    if ($token === '' && isset($_GET['access_token'])) {
        $token = trim((string)$_GET['access_token']);
    }

    return $token;
}

/**
 * Resolve company from Bearer token.
 */
function resolve_company_id_from_token(PDO $pdo): int
{
    $token = get_auth_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 SCIM path from PATH_INFO or REQUEST_URI.
 */
function get_scim_path(): string
{
    // Preferred: PATH_INFO (for scim.php/Users style URLs)
    if (!empty($_SERVER['PATH_INFO'])) {
        return rtrim($_SERVER['PATH_INFO'], '/');
    }

    // Fallback: derive from REQUEST_URI after scim.php
    $uri = parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH) ?? '';
    $pos = strpos($uri, 'scim.php');
    if ($pos !== false) {
        $suffix = substr($uri, $pos + strlen('scim.php'));
        return rtrim($suffix, '/');
    }

    return '';
}

/**
 * Build SCIM User 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] ?? '';
    }

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

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

    return $res;
}

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

/* ============================================================================
 * Routing
 * ========================================================================== */

$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
$path   = get_scim_path();

// Service root: scim.php or scim.php/
if ($path === '' || $path === '/') {
    // Return minimal valid SCIM list so Entra doesn't see 404
    send_scim_list([], 1, 0);
}

// ServiceProviderConfig
if (strcasecmp($path, '/ServiceProviderConfig') === 0) {
    echo json_encode([
        'schemas' => ['urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig'],
        'patch'   => ['supported' => true],
        'bulk'    => ['supported' => false],
        'filter'  => ['supported' => true, 'maxResults' => 200],
        'changePassword' => ['supported' => false],
        'sort'    => ['supported' => true],
        'etag'    => ['supported' => false],
        'authenticationSchemes' => [[
            'type'        => 'oauthbearertoken',
            'name'        => 'OAuth Bearer Token',
            'description' => 'Bearer token as configured per company in TowLens.',
        ]],
    ]);
    exit;
}

// Schemas
if (strcasecmp($path, '/Schemas') === 0) {
    echo json_encode([
        'schemas'      => ['urn:ietf:params:scim:api:messages:2.0:ListResponse'],
        'totalResults' => 1,
        'startIndex'   => 1,
        'itemsPerPage' => 1,
        'Resources'    => [[
            'id'          => 'urn:ietf:params:scim:schemas:core:2.0:User',
            'name'        => 'User',
            'description' => 'User Account',
            'attributes'  => [
                ['name' => 'userName',   'type' => 'string',  'multiValued' => false],
                ['name' => 'externalId', 'type' => 'string',  'multiValued' => false],
                ['name' => 'name',       'type' => 'complex', 'multiValued' => false],
                ['name' => 'emails',     'type' => 'complex', 'multiValued' => true],
                ['name' => 'active',     'type' => 'boolean','multiValued' => false],
            ],
        ]],
    ]);
    exit;
}

/* ============================ Users ================================= */

// Anything under /Users requires auth + company_id
if (stripos($path, '/Users') === 0) {
    $companyId = resolve_company_id_from_token($pdo);

    // Single user?
    $userId = null;
    if (preg_match('#^/Users/([^/]+)$#i', $path, $m)) {
        $userId = $m[1];
    }

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

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

            $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;
        }

        // List / filter
        $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];

        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);

        $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) ========== */

    if ($method === 'POST' && $userId === 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;

        $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 ($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, 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,
            ':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) ========== */

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

        $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'] ?? ''));
            $pathOp = isset($op['path']) ? trim($op['path']) : '';
            $value  = $op['value'] ?? null;

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

            if ($pathOp === '' && 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($pathOp, 'active') === 0) {
                $active = (bool)$value;
            } elseif (strcasecmp($pathOp, 'userName') === 0) {
                $email = trim((string)$value);
            } elseif (stripos($pathOp, 'name.givenName') === 0 && is_string($value)) {
                $parts = preg_split('/\s+/', $display);
                $family = $parts[1] ?? '';
                $display = trim($value . ' ' . $family);
            } elseif (stripos($pathOp, '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) ========== */

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

        $stmt = $pdo->prepare("
            SELECT id
            FROM users
            WHERE id = ?
              AND company_id = ?
            LIMIT 1
        ");
        $stmt->execute([$uid, $companyId]);
        $row = $stmt->fetch(PDO::FETCH_ASSOC);
        if (!$row) {
            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]);

        http_response_code(204);
        exit;
    }

    // Unsupported method on /Users
    scim_error(405, 'Method not allowed', 'invalidSyntax');
}

/* ============================================================================
 * Fallback
 * ========================================================================== */

scim_error(404, 'Not found', 'invalidValue');
