from uuid import UUID

import pytest
from fastapi import HTTPException, status
from fastapi.security import HTTPAuthorizationCredentials
from pytest_mock import MockFixture
from src.__types.index import Tenant
from src.routers.management.authorizer import resolver

RESOLVER_PATH = "src.routers.management.authorizer.resolver."


def assert_invalid_token(exc: pytest.ExceptionInfo[HTTPException]) -> None:
    assert exc.value.status_code == status.HTTP_401_UNAUTHORIZED
    assert exc.value.detail == "Invalid token."


class TestResolver:
    def test_get_tenant_routes_access_token_to_m2m_authorizer(
        self,
        mocker: MockFixture,
        credential: HTTPAuthorizationCredentials,
        mock_tenant: Tenant,
    ) -> None:
        """アクセストークンの場合、get_m2m_tenantが呼ばれること"""
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={"token_use": "access"},
        )
        get_m2m = mocker.patch(f"{RESOLVER_PATH}get_m2m_tenant", return_value=mock_tenant)
        get_user = mocker.patch(f"{RESOLVER_PATH}get_user_tenant")

        tenant = resolver.get_tenant(credential=credential, header_tenant_name=None)

        assert tenant == mock_tenant
        get_m2m.assert_called_once_with(credential=credential)
        get_user.assert_not_called()

    def test_get_tenant_routes_id_token_to_user_authorizer(
        self,
        mocker: MockFixture,
        credential: HTTPAuthorizationCredentials,
        mock_tenant: Tenant,
    ) -> None:
        """IDトークンの場合、get_user_tenantが呼ばれること"""
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={"token_use": "id"},
        )
        get_m2m = mocker.patch(f"{RESOLVER_PATH}get_m2m_tenant")
        get_user = mocker.patch(f"{RESOLVER_PATH}get_user_tenant", return_value=mock_tenant)

        tenant = resolver.get_tenant(credential=credential, header_tenant_name="tenant-name")

        assert tenant == mock_tenant
        get_m2m.assert_not_called()
        get_user.assert_called_once_with(
            credential=credential,
            header_tenant_name="tenant-name",
        )

    def test_get_tenant_raises_when_token_use_is_invalid(
        self,
        mocker: MockFixture,
        credential: HTTPAuthorizationCredentials,
    ) -> None:
        """token_useが無効な場合、HTTPExceptionが発生すること"""
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={"token_use": "invalid"},
        )

        with pytest.raises(HTTPException) as exc:
            resolver.get_tenant(credential=credential, header_tenant_name=None)

        assert_invalid_token(exc)

    def test_get_tenant_raises_when_token_use_claim_missing(
        self,
        mocker: MockFixture,
        credential: HTTPAuthorizationCredentials,
    ) -> None:
        """token_useクレームがない場合、HTTPExceptionが発生すること"""
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={},
        )

        with pytest.raises(HTTPException) as exc:
            resolver.get_tenant(credential=credential, header_tenant_name=None)

        assert_invalid_token(exc)


class TestGetCurrentUserId:
    def test_returns_user_id(self, mocker: MockFixture, credential: HTTPAuthorizationCredentials) -> None:
        """custom:user_idクレームが存在する場合、UUIDとして返すこと"""
        user_id = "00000001-0000-0000-0000-000000000001"
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={"custom:user_id": user_id},
        )

        result = resolver.get_user_id(credential=credential)

        assert result == UUID(user_id)

    def test_raises_403_when_user_id_missing(
        self,
        mocker: MockFixture,
        credential: HTTPAuthorizationCredentials,
    ) -> None:
        """custom:user_idクレームがない場合 (m2mトークン)、403を返すこと"""
        mocker.patch(
            f"{RESOLVER_PATH}jwt.get_unverified_claims",
            return_value={},
        )

        with pytest.raises(HTTPException) as exc:
            resolver.get_user_id(credential=credential)

        assert exc.value.status_code == status.HTTP_403_FORBIDDEN
        assert exc.value.detail == "Forbidden."
