<?php

namespace MintyPHP\Repository\Auth;

use MintyPHP\DB;
use MintyPHP\Repository\Support\RepoQuery;

/** Persists API tokens with selector/hash pairs, expiry tracking, and usage timestamps. */
class ApiTokenRepository implements ApiTokenRepositoryInterface
{
    private const UUID_REGEX = '/^[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/i';

    private function unwrapList($rows): array
    {
        if (!is_array($rows)) {
            return [];
        }
        $list = [];
        foreach ($rows as $row) {
            $data = $row['user_api_tokens'] ?? $row;
            if (is_array($data)) {
                $list[] = $data;
            }
        }
        return $list;
    }

    public function isUuid(string $value): bool
    {
        return (bool) preg_match(self::UUID_REGEX, trim($value));
    }

    public function create(
        int $userId,
        string $name,
        string $selector,
        string $tokenHash,
        ?int $tenantId,
        ?string $expiresAt,
        ?int $createdBy
    ): ?int {
        $id = DB::insert(
            'insert into user_api_tokens (uuid, user_id, name, selector, token_hash, tenant_id, expires_at, created_by, created) values (?,?,?,?,?,?,?,?,NOW())',
            RepoQuery::uuidV4(),
            (string) $userId,
            $name,
            $selector,
            $tokenHash,
            $tenantId !== null ? (string) $tenantId : null,
            $expiresAt,
            $createdBy !== null ? (string) $createdBy : null
        );
        return $id ? (int) $id : null;
    }

    public function findBySelector(string $selector): ?array
    {
        $row = DB::selectOne(
            'select id, uuid, user_id, name, selector, token_hash, tenant_id, last_used_at, last_ip, expires_at, revoked_at, created_by, created from user_api_tokens where selector = ? limit 1',
            $selector
        );
        if (!$row || !isset($row['user_api_tokens'])) {
            return null;
        }
        return $row['user_api_tokens'];
    }

    public function find(int $id): ?array
    {
        $row = DB::selectOne(
            'select id, uuid, user_id, name, selector, tenant_id, last_used_at, last_ip, expires_at, revoked_at, created_by, created from user_api_tokens where id = ? limit 1',
            (string) $id
        );
        if (!$row || !isset($row['user_api_tokens'])) {
            return null;
        }
        return $row['user_api_tokens'];
    }

    public function findByUuid(string $uuid): ?array
    {
        $uuid = trim($uuid);
        if (!$this->isUuid($uuid)) {
            return null;
        }

        $row = DB::selectOne(
            'select id, uuid, user_id, name, selector, tenant_id, last_used_at, last_ip, expires_at, revoked_at, created_by, created from user_api_tokens where uuid = ? limit 1',
            $uuid
        );
        if (!$row || !isset($row['user_api_tokens'])) {
            return null;
        }
        return $row['user_api_tokens'];
    }

    public function findByUuidForUser(string $uuid, int $userId): ?array
    {
        $uuid = trim($uuid);
        if ($userId <= 0 || !$this->isUuid($uuid)) {
            return null;
        }

        $row = DB::selectOne(
            'select id, uuid, user_id, name, selector, tenant_id, last_used_at, last_ip, expires_at, revoked_at, created_by, created from user_api_tokens where uuid = ? and user_id = ? limit 1',
            $uuid,
            (string) $userId
        );
        if (!$row || !isset($row['user_api_tokens'])) {
            return null;
        }
        return $row['user_api_tokens'];
    }

    public function updateLastUsed(int $id, string $ip): bool
    {
        $result = DB::update(
            'update user_api_tokens set last_used_at = NOW(), last_ip = ? where id = ?',
            $ip,
            (string) $id
        );
        return $result !== false;
    }

    public function revoke(int $id): bool
    {
        $result = DB::update(
            'update user_api_tokens set revoked_at = NOW() where id = ? and revoked_at is null',
            (string) $id
        );
        return $result !== false;
    }

    public function revokeByUuidForUser(string $uuid, int $userId): bool
    {
        $uuid = trim($uuid);
        if ($userId <= 0 || !$this->isUuid($uuid)) {
            return false;
        }

        $result = DB::update(
            'update user_api_tokens set revoked_at = NOW() where uuid = ? and user_id = ? and revoked_at is null',
            $uuid,
            (string) $userId
        );
        return $result !== false;
    }

    public function revokeAllForUser(int $userId, ?int $tenantId = null): int
    {
        if ($tenantId !== null && $tenantId > 0) {
            $result = DB::update(
                'update user_api_tokens set revoked_at = NOW() where user_id = ? and (tenant_id = ? or tenant_id is null) and revoked_at is null',
                (string) $userId,
                (string) $tenantId
            );
        } else {
            $result = DB::update(
                'update user_api_tokens set revoked_at = NOW() where user_id = ? and revoked_at is null',
                (string) $userId
            );
        }
        return $result !== false ? (int) $result : 0;
    }

    public function revokeAllActiveByAdmin(): int
    {
        $result = DB::update(
            'update user_api_tokens set revoked_at = NOW() where revoked_at is null and (expires_at is null or expires_at > NOW())'
        );
        return $result !== false ? (int) $result : 0;
    }

    public function listByUserId(int $userId, int $limit = 25): array
    {
        if ($userId <= 0) {
            return [];
        }
        if ($limit < 1) {
            $limit = 25;
        } elseif ($limit > 100) {
            $limit = 100;
        }
        $rows = DB::select(
            'select id, uuid, user_id, name, selector, tenant_id, last_used_at, last_ip, expires_at, revoked_at, created_by, created from user_api_tokens where user_id = ? order by id desc limit ?',
            (string) $userId,
            (string) $limit
        );
        return $this->unwrapList($rows);
    }

    public function countActiveForUser(int $userId): int
    {
        $count = DB::selectValue(
            'select count(*) from user_api_tokens where user_id = ? and revoked_at is null and (expires_at is null or expires_at > NOW())',
            (string) $userId
        );
        return $count ? (int) $count : 0;
    }

    public function countActiveForUserExcludingId(int $userId, int $excludeId): int
    {
        if ($userId <= 0) {
            return 0;
        }

        $count = DB::selectValue(
            'select count(*) from user_api_tokens where user_id = ? and id != ? and revoked_at is null and (expires_at is null or expires_at > NOW())',
            (string) $userId,
            (string) $excludeId
        );
        return $count ? (int) $count : 0;
    }

    public function countActive(): int
    {
        $count = DB::selectValue(
            'select count(*) from user_api_tokens where revoked_at is null and (expires_at is null or expires_at > NOW())'
        );
        return $count ? (int) $count : 0;
    }

}
