<?php

namespace MintyPHP\Service\Auth;

use MintyPHP\Repository\Auth\ApiTokenRepository;
use MintyPHP\Repository\Tenant\TenantRepository;
use MintyPHP\Service\Tenant\TenantScopeService;

class ApiTokenEndpointService
{
    public function __construct(
        private readonly ApiTokenRepository $apiTokenRepository,
        private readonly TenantRepository $tenantRepository,
        private readonly TenantScopeService $authScopeGateway
    ) {
    }

    public function normalizeLimit(mixed $rawLimit, int $default = 50, int $min = 1, int $max = 100): int
    {
        $limit = (int) $rawLimit;
        if ($limit < $min) {
            return $default;
        }
        if ($limit > $max) {
            return $max;
        }
        return $limit;
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    public function listSelfTokens(int $userId, int $limit, ?int $scopedTenantId, ?int $currentTokenId): array
    {
        if ($userId <= 0) {
            return [];
        }

        $tokens = $this->apiTokenRepository->listByUserId($userId, $limit);
        $tenantUuidById = $this->buildTenantUuidByIdMap($tokens);
        $rows = [];

        foreach ($tokens as $token) {
            $tokenTenantId = $this->normalizeTenantId($token['tenant_id'] ?? null);
            if ($scopedTenantId !== null && $scopedTenantId > 0 && $tokenTenantId !== $scopedTenantId) {
                continue;
            }

            $rows[] = [
                'uuid' => (string) ($token['uuid'] ?? ''),
                'name' => (string) ($token['name'] ?? ''),
                'selector_prefix' => substr((string) ($token['selector'] ?? ''), 0, 8),
                'tenant_uuid' => $tokenTenantId !== null ? ($tenantUuidById[$tokenTenantId] ?? null) : null,
                'last_used_at' => $token['last_used_at'] ?? null,
                'last_ip' => (string) ($token['last_ip'] ?? ''),
                'expires_at' => $token['expires_at'] ?? null,
                'revoked_at' => $token['revoked_at'] ?? null,
                'created' => $token['created'] ?? null,
                'is_current_token' => (int) ($token['id'] ?? 0) === (int) ($currentTokenId ?? 0),
            ];
        }

        return $rows;
    }

    /**
     * @return array<string, mixed>|null
     */
    public function findSelfToken(string $tokenUuid, int $userId, ?int $scopedTenantId, ?int $currentTokenId): ?array
    {
        $tokenUuid = trim($tokenUuid);
        if ($userId <= 0 || !$this->apiTokenRepository->isUuid($tokenUuid)) {
            return null;
        }

        $token = $this->apiTokenRepository->findByUuidForUser($tokenUuid, $userId);
        if (!$token) {
            return null;
        }

        $tokenTenantId = $this->normalizeTenantId($token['tenant_id'] ?? null);
        if ($scopedTenantId !== null && $scopedTenantId > 0 && $tokenTenantId !== $scopedTenantId) {
            return null;
        }

        return [
            'uuid' => (string) ($token['uuid'] ?? ''),
            'name' => (string) ($token['name'] ?? ''),
            'selector_prefix' => substr((string) ($token['selector'] ?? ''), 0, 8),
            'tenant_uuid' => $this->resolveTenantUuidById($tokenTenantId),
            'last_used_at' => $token['last_used_at'] ?? null,
            'last_ip' => (string) ($token['last_ip'] ?? ''),
            'expires_at' => $token['expires_at'] ?? null,
            'revoked_at' => $token['revoked_at'] ?? null,
            'created' => $token['created'] ?? null,
            'is_current_token' => (int) ($token['id'] ?? 0) === (int) ($currentTokenId ?? 0),
        ];
    }

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

        return $this->apiTokenRepository->revokeByUuidForUser($tokenUuid, $userId);
    }

    /**
     * @return array{ok: bool, tenant_id?: int|null, tenant_uuid?: string|null, status?: int, error?: string, field?: string}
     */
    public function resolveSelfCreateTenant(int $userId, ?int $scopedTenantId, string $tenantUuidInput): array
    {
        $tenantUuidInput = trim($tenantUuidInput);
        $tenantId = null;
        $tenantUuid = null;

        if ($tenantUuidInput !== '') {
            $tenant = $this->tenantRepository->findByUuid($tenantUuidInput);
            $tenantId = (int) ($tenant['id'] ?? 0);
            $tenantUuid = trim((string) ($tenant['uuid'] ?? ''));
            if ($tenantId <= 0 || $tenantUuid === '') {
                return [
                    'ok' => false,
                    'status' => 422,
                    'error' => 'invalid_tenant_uuid',
                    'field' => 'tenant_uuid',
                ];
            }
        }

        if ($scopedTenantId !== null && $scopedTenantId > 0) {
            $scopedTenantUuid = $this->resolveTenantUuidById($scopedTenantId);
            if ($scopedTenantUuid === null) {
                return ['ok' => false, 'status' => 403, 'error' => 'tenant_scoped_token_forbidden'];
            }
            if ($tenantId !== null && $tenantId !== $scopedTenantId) {
                return ['ok' => false, 'status' => 403, 'error' => 'tenant_scoped_token_forbidden'];
            }

            $tenantId = $scopedTenantId;
            $tenantUuid = $scopedTenantUuid;
        }

        if ($tenantId !== null) {
            $tenantIds = $this->authScopeGateway->getUserTenantIds($userId);
            if (!in_array($tenantId, $tenantIds, true)) {
                return ['ok' => false, 'status' => 403, 'error' => 'tenant_not_assigned'];
            }
        }

        return [
            'ok' => true,
            'tenant_id' => $tenantId,
            'tenant_uuid' => $tenantUuid,
        ];
    }

    /**
     * @return array{ok: bool, expires_at?: string|null, error?: string}
     */
    public function parseExpiresAt(string $expiresInput): array
    {
        $expiresInput = trim($expiresInput);
        if ($expiresInput === '') {
            return ['ok' => true, 'expires_at' => null];
        }

        try {
            $expiresDate = new \DateTimeImmutable($expiresInput);
            $expiresDateUtc = $expiresDate->setTimezone(new \DateTimeZone('UTC'));
            $nowUtc = new \DateTimeImmutable('now', new \DateTimeZone('UTC'));
            if ($expiresDateUtc <= $nowUtc) {
                return ['ok' => false, 'error' => 'must_be_in_future'];
            }
            return ['ok' => true, 'expires_at' => $expiresDateUtc->format('Y-m-d H:i:s')];
        } catch (\Throwable $exception) {
            return ['ok' => false, 'error' => 'invalid_datetime'];
        }
    }

    public function capExpiryToParent(?string $requestedExpiresAt, ?string $parentExpiresAt): ?string
    {
        $parentExpiresAt = trim((string) ($parentExpiresAt ?? ''));
        if ($parentExpiresAt === '') {
            return $requestedExpiresAt;
        }

        try {
            $parentExpiry = new \DateTimeImmutable($parentExpiresAt, new \DateTimeZone('UTC'));
            if ($requestedExpiresAt === null || trim($requestedExpiresAt) === '') {
                return $parentExpiresAt;
            }

            $requestedExpiry = new \DateTimeImmutable($requestedExpiresAt, new \DateTimeZone('UTC'));
            if ($requestedExpiry > $parentExpiry) {
                return $parentExpiresAt;
            }
        } catch (\Throwable $exception) {
            return $requestedExpiresAt;
        }

        return $requestedExpiresAt;
    }

    /**
     * @return array{ok: bool, tenant_id?: int|null, error?: string, status?: int}
     */
    public function resolveLoginTenant(int $userId, string $tenantUuidInput): array
    {
        $tenantUuidInput = trim($tenantUuidInput);
        if ($tenantUuidInput === '') {
            return ['ok' => true, 'tenant_id' => null];
        }

        $tenant = $this->tenantRepository->findByUuid($tenantUuidInput);
        $tenantId = (int) ($tenant['id'] ?? 0);
        if ($tenantId <= 0) {
            return ['ok' => false, 'error' => 'invalid_tenant_uuid', 'status' => 422];
        }

        $userTenantIds = $this->authScopeGateway->getUserTenantIds($userId);
        if (!in_array($tenantId, $userTenantIds, true)) {
            return ['ok' => false, 'error' => 'tenant_not_assigned', 'status' => 403];
        }

        return ['ok' => true, 'tenant_id' => $tenantId];
    }

    public function resolveTenantUuidById(?int $tenantId): ?string
    {
        $tenantId = (int) ($tenantId ?? 0);
        if ($tenantId <= 0) {
            return null;
        }

        $tenant = $this->tenantRepository->find($tenantId);
        $tenantUuid = trim((string) ($tenant['uuid'] ?? ''));

        return $tenantUuid !== '' ? $tenantUuid : null;
    }

    /**
     * @param array<int, array<string, mixed>> $tokens
     * @return array<int, string>
     */
    private function buildTenantUuidByIdMap(array $tokens): array
    {
        $tenantIds = [];
        foreach ($tokens as $token) {
            $tenantId = $this->normalizeTenantId($token['tenant_id'] ?? null);
            if ($tenantId !== null) {
                $tenantIds[] = $tenantId;
            }
        }
        $tenantIds = array_values(array_unique($tenantIds));
        if (!$tenantIds) {
            return [];
        }

        $map = [];
        foreach ($this->tenantRepository->listByIds($tenantIds) as $tenant) {
            $tenantId = (int) ($tenant['id'] ?? 0);
            $tenantUuid = trim((string) ($tenant['uuid'] ?? ''));
            if ($tenantId > 0 && $tenantUuid !== '') {
                $map[$tenantId] = $tenantUuid;
            }
        }
        return $map;
    }

    private function normalizeTenantId(mixed $tenantId): ?int
    {
        $tenantId = (int) ($tenantId ?? 0);
        return $tenantId > 0 ? $tenantId : null;
    }
}
