<?php

namespace routes;

use classes\authentication;
use objects\logs_o;
use objects\passkeys_o;
use traits\route_t;

class passkeysRoute
{
    use route_t;

    /**
     * @return array{principal:object,user_id:int,is_subuser:bool}
     */
    private function resolvePasskeyPrincipal(string $classicUserPermission): array
    {
        global $response;

        $auth = new authentication();
        $subuser = $auth->get_subuser();
        if ($subuser !== false) {
            return [
                'principal' => $subuser,
                'user_id' => (int)$subuser->id,
                'is_subuser' => true,
            ];
        }

        self::requirePermission($classicUserPermission);
        $user = $auth->get_user();
        if (!$user) {
            (new logs_o())->add('user_security', 'global', 0, 0, 'USER_SECURITY_PASSKEYS_AUTH', 'User not logged in');
            $response->error('Invalid session', 400);
        }

        return [
            'principal' => $user,
            'user_id' => (int)$user->id,
            'is_subuser' => false,
        ];
    }

    public function run(): void
    {
        // List passkeys for current authenticated user
        $this->get('/account/security/passkeys', function () {
            global $response;
            $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_list');

            $passkeys = new passkeys_o();
            $passkeys->setAdditionalWhereClause(
                '`user_id` = ' . (int)$principal['user_id'] . ' AND `is_subuser` = ' . ($principal['is_subuser'] ? '1' : '0')
            );
            $list = $passkeys->listObjectsWithPaginationIfSet(function ($o) {
                // $o is an associative array from the database
                $transports = null;
                if (isset($o['transports'])) {
                    $decoded = json_decode($o['transports'], true);
                    $transports = is_array($decoded) ? $decoded : null;
                }
                return [
                    'id' => isset($o['id']) ? (int)$o['id'] : null,
                    'credential_id' => $o['credential_id'] ?? null,
                    'name' => $o['name'] ?? null,
                    'algorithm' => $o['algorithm'] ?? null,
                    'transports' => $transports,
                    'sign_count' => isset($o['sign_count']) ? (int)$o['sign_count'] : null,
                    'created_at' => $o['created_at'] ?? null,
                    'updated_at' => $o['updated_at'] ?? null,
                ];
            });

            (new logs_o())->add('user_security', 'global', 1, $principal['user_id'], 'USER_SECURITY_PASSKEYS_LIST', 'Listed passkeys');
            $response->success($list);
        }, [
            'user_security_passkeys_list' => 'List passkeys for the authenticated user',
        ]);

        // Create/add a passkey (store after client-side WebAuthn attestation)
        $this->post('/account/security/passkeys', function () {
            global $response;
            $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_create');

            self::requireParameters(['credential_id', 'public_key', 'algorithm', 'transports']);

            $credential_id = (string)self::getParameter('credential_id');
            self::requireType($credential_id, self::type_string());
            self::requireMinLength('credential_id', 16);
            self::requireMaxLength('credential_id', 4096);

            $algorithm = (string)self::getParameter('algorithm');
            self::requireType($algorithm, self::type_string());
            self::requireMinLength('algorithm', 3);
            self::requireMaxLength('algorithm', 32);

            $public_key = (string)self::getParameter('public_key');
            self::requireType($public_key, self::type_string());
            self::requireMinLength('public_key', 32);
            self::requireMaxLength('public_key', 16384);

            // Normalize public key to COSE (some clients send full attestation object instead of just the key)
            try {
                $wa = new \classes\webauthn();
                $decoded_pk = $wa->b64urlDecode($public_key);
                $normalized_pk = $wa->ensureCosePublicKey($decoded_pk, $algorithm);
                $public_key = rtrim(strtr(base64_encode($normalized_pk), '+/', '-_'), '=');
            } catch (\Exception $e) {
                // If normalization fails, we continue with original public_key and let verification handle it later if possible
            }

            $transports = self::getParameter('transports');
            self::requireType($transports, self::TYPE_ARRAY());

            $name = self::getParameter('name');
            if ($name !== null) {
                $name = (string)$name;
                self::requireType($name, self::type_string());
                self::requireMinLength('name', 1);
                self::requireMaxLength('name', 255);
            }

            $obj = new passkeys_o();
            $obj->add((int)$principal['user_id'], (bool)$principal['is_subuser'], $credential_id, $public_key, $algorithm, (array)$transports, $name);

            (new logs_o())->add('user_security', 'global', 1, $principal['user_id'], 'USER_SECURITY_PASSKEYS_CREATE', 'Created passkey: ' . $obj->id);
            $response->success(['id' => $obj->id]);
        }, [
            'user_security_passkeys_create' => 'Create/add a new passkey for the authenticated user',
        ]);

        // Rename a passkey
        $this->patch('/account/security/passkeys/{id}', function () {
            global $response;
            $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_rename');

            $id = (int)self::fromRoute('id');
            self::requireParameterIntPositive($id, 'id');
            self::requireParameters(['name']);
            $name = (string)self::getParameter('name');
            self::requireType($name, self::type_string());
            self::requireMinLength('name', 1);
            self::requireMaxLength('name', 255);

            $obj = (new passkeys_o())->select($id);
            if (
                !$obj->exists()
                || (int)$obj->user_id->value() !== (int)$principal['user_id']
                || (bool)$obj->is_subuser->value() !== (bool)$principal['is_subuser']
            ) {
                (new logs_o())->add('user_security', 'global', 0, $principal['user_id'], 'USER_SECURITY_PASSKEYS_RENAME', 'Passkey not found or not owned');
                $response->error('Not found', 404);
            }

            $obj->update(['name' => $name]);
            (new logs_o())->add('user_security', 'global', 1, $principal['user_id'], 'USER_SECURITY_PASSKEYS_RENAME', 'Renamed passkey ' . $id);
            $response->success(['message' => 'Renamed', 'id' => $id]);
        }, [
            'user_security_passkeys_rename' => 'Rename a passkey that belongs to the authenticated user',
        ]);

        // Delete a passkey (soft delete)
        $this->delete('/account/security/passkeys/{id}', function () {
            global $response;
            $principal = $this->resolvePasskeyPrincipal('user_security_passkeys_delete');

            $id = (int)self::fromRoute('id');
            self::requireParameterIntPositive($id, 'id');

            $obj = (new passkeys_o())->select($id);
            if (
                !$obj->exists()
                || (int)$obj->user_id->value() !== (int)$principal['user_id']
                || (bool)$obj->is_subuser->value() !== (bool)$principal['is_subuser']
            ) {
                (new logs_o())->add('user_security', 'global', 0, $principal['user_id'], 'USER_SECURITY_PASSKEYS_DELETE', 'Passkey not found or not owned');
                $response->error('Not found', 404);
            }

            $obj->delete();
            (new logs_o())->add('user_security', 'global', 1, $principal['user_id'], 'USER_SECURITY_PASSKEYS_DELETE', 'Deleted passkey ' . $id);
            $response->success(['message' => 'Deleted', 'id' => $id]);
        }, [
            'user_security_passkeys_delete' => 'Delete a passkey that belongs to the authenticated user',
        ]);
    }
}
