from typing import Annotated
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, status
from fastapi.responses import Response
from psycopg import Connection
from pydantic import BaseModel, Field
from src.__types.errors import PriceCodeNotFoundError
from src.__types.index import Client, ClientTenant, Tenant, UsageEventRequestPayload
from src.infrastructure.clients.s3_client import S3Client
from src.infrastructure.clients.sqs_client import SqsClient
from src.infrastructure.usage_event_request_queue import UsageEventRequestQueue
from src.infrastructure.usage_event_request_storage import UsageEventRequestStorage
from src.routers.client.authorizer import get_client, get_client_tenant, get_tenant
from src.routers.client.connection import get_connection, set_session_variables
from src.routers.client.headers import get_idempotency_key
from src.settings import Settings
from src.usecases.usage_event_usecase import UsageEventUseCase

settings = Settings()
router = APIRouter(
    prefix="/usage-events",
    tags=["使用イベント"],
)

POST_DESCRIPTION = """
使用イベントを送信します。

生成AI機能の実行後に、使用区分コード（何を利用したか）、使用量（どれだけ利用したか）をリクエストしてください。


### エラーハンドリング

| エラー内容 | 対応 |
| --- | --- |
| 4xxエラー （429以外） | 生成AI機能の処理を中断 |
| 429エラー, 5xxエラー, ネットワークエラー | リトライ戦略に従ってリトライ |

### リトライ戦略


| 観点 | 推奨される対応 |
| --- | --- |
| リトライ回数 | 最低`3回` |
| リトライ間隔 | Exponential Backoff |
| リクエストタイムアウト | 最低`10秒` |
| エラー時の対応 | リトライを経ても失敗する場合は、AIユニットの障害とみなし、送信を諦める |

### リクエスト例

```python
# --- 変数定義 ---
endpoint = "https://ai-unit.lab.works-hi.co.jp/v1/usage-events"
headers = {
    "X-Api-Key": "your-api-key",
    "X-Tenant-Name": "your-tenant-name",
    "X-Idempotency-Key": str(uuid.uuid4()),
}
max_retries = 3
wait_times = [1, 2, 4]

# --- リトライ処理 ---
try:
    usage = bedrock_response["usage"]
    payload = {
        "events": [
            {
                "code": "aws.ap-northeast-1.APN1-Claude3Haiku-input-tokens",
                "quantity": usage["inputTokens"],
                "service": "bizmatch", "feature": "employee_search",
                "userId": "クライアントのユーザーID", "traceId": "クライアント側で生成したトレースID",
            },
            {
                "code": "aws.ap-northeast-1.APN1-Claude3Haiku-output-tokens",
                "quantity": usage["outputTokens"],
                "service": "bizmatch", "feature": "employee_search",
                "userId": "クライアントのユーザーID", "traceId": "クライアント側で生成したトレースID",
            },
        ]
    }
    for attempt in range(max_retries + 1):
        try:
            response = requests.post(endpoint, headers=headers, json=payload, timeout=10)

            if response.status_code = 202:
                logger.info("使用量イベントの送信に成功しました。")
                break
            elif response.status_code in [400, 401, 403, 422]:
                raise AIUnitErrorException
            elif response.status_code in [429]:
                logger.warning(f"AIユニットでレートリミットエラーが発生、リトライします。")
            elif 500 <= response.status_code <= 599:
                logger.warning(f"AIユニットでサーバーエラーが発生、リトライします。")
            else:
                raise AIUnitErrorException

        except requests.exceptions.RequestException:
            logger.warning("AIユニットへのリクエストでネットワークエラーが発生、リトライします")

        if attempt < max_retries:
            time.sleep(wait_times[attempt])
    else:
        logger.warning("AIユニットへのリトライが全て失敗しました。障害とみなし、処理を続行します。")
```
"""  # noqa: RUF001


class UsageEventRequest(BaseModel):
    """使用イベントのリクエストモデル"""

    code: str = Field(..., description="使用区分")
    quantity: str = Field(..., description="使用量")
    service: str = Field(..., description="サービス名")
    feature: str = Field(..., description="機能名")
    user_id: str | None = Field(default=None, description="ユーザーID", alias="userId")
    trace_id: str | None = Field(default=None, description="トレースID", alias="traceId")


class PostRequest(BaseModel):
    """使用イベント送信のリクエストモデル"""

    events: list[UsageEventRequest] = Field(..., description="使用イベントのリスト")


@router.post(
    "",
    summary="送信",
    description=POST_DESCRIPTION,
    status_code=status.HTTP_202_ACCEPTED,
    responses={
        status.HTTP_202_ACCEPTED: {
            "description": "使用イベントの受付",
        },
        status.HTTP_400_BAD_REQUEST: {
            "description": "必須ヘッダの不足や存在しない使用区分等、の不正リクエスト",
        },
        status.HTTP_401_UNAUTHORIZED: {
            "description": "認証失敗",
        },
        status.HTTP_403_FORBIDDEN: {
            "description": "権限の不足",
        },
        status.HTTP_422_UNPROCESSABLE_CONTENT: {
            "description": "リクエストボディのバリデーションエラー",
        },
        status.HTTP_429_TOO_MANY_REQUESTS: {
            "description": "レートリミットエラー ※ リトライを推奨します。",
        },
        status.HTTP_500_INTERNAL_SERVER_ERROR: {
            "description": "内部エラー ※ AIユニットの障害発生時には機能の利用を続行してください。",
        },
    },
)
async def post(  # noqa: PLR0913
    tenant: Annotated[Tenant, Depends(get_tenant)],
    client: Annotated[Client, Depends(get_client)],
    client_tenant: Annotated[ClientTenant, Depends(get_client_tenant)],
    idempotency_key: Annotated[UUID, Depends(get_idempotency_key)],
    request: PostRequest,
    connection: Annotated[Connection, Depends(get_connection)],
) -> Response:
    # バックグラウンドでの処理の登録前に可能な限りのバリデーションを行う
    usecase = UsageEventUseCase.create(tenant=tenant, connection=connection)
    set_session_variables(tenant=tenant, connection=connection)
    try:
        for event in request.events:
            usecase.validate(event.code)
    except PriceCodeNotFoundError as e:
        raise HTTPException(status_code=400, detail="Invalid code.") from e

    usecase.accept(
        request=UsageEventRequestPayload(
            tenant=tenant,
            client=client,
            client_tenant=client_tenant,
            idempotency_key=idempotency_key,
            request_events=[event.model_dump(mode="json") for event in request.events],
        ),
        storage=UsageEventRequestStorage(client=S3Client(tenant_id=tenant.id)),
        queue=UsageEventRequestQueue(client=SqsClient()),
    )

    return Response(status_code=status.HTTP_202_ACCEPTED)
