import uuid
from typing import Any, Protocol

import httpx
from pydantic import BaseModel, Field, ValidationError

from .config import get_settings
from .errors import AppError


class AIPlanStep(BaseModel):
    step_id: str = Field(min_length=1, max_length=80)
    tool_name: str = Field(min_length=3, max_length=120)
    arguments: dict[str, Any]
    concise_rationale: str = Field(min_length=1, max_length=500)
    depends_on: list[str] = Field(default_factory=list, max_length=20)


class AIPlan(BaseModel):
    summary: str = Field(min_length=1, max_length=1000)
    steps: list[AIPlanStep] = Field(default_factory=list, max_length=25)
    response_outline: str = Field(min_length=1, max_length=1000)


class AIUsage(BaseModel):
    input_tokens: int = Field(default=0, ge=0)
    output_tokens: int = Field(default=0, ge=0)
    total_tokens: int = Field(default=0, ge=0)


class AIPlanResult(BaseModel):
    provider: str = Field(min_length=1, max_length=40)
    model: str = Field(min_length=1, max_length=160)
    provider_response_id: str | None = Field(default=None, max_length=160)
    plan: AIPlan
    usage: AIUsage = Field(default_factory=AIUsage)


class PlanClient(Protocol):
    async def create_plan(
        self, *, execution_id: uuid.UUID, workspace_id: uuid.UUID, actor_id: uuid.UUID,
        instruction: str, allowed_tools: list[str], safety_identifier: str, request_id: str,
        model: str | None = None,
    ) -> AIPlanResult: ...


class AIPlanClient:
    async def create_plan(
        self, *, execution_id: uuid.UUID, workspace_id: uuid.UUID, actor_id: uuid.UUID,
        instruction: str, allowed_tools: list[str], safety_identifier: str, request_id: str,
        model: str | None = None,
    ) -> AIPlanResult:
        settings = get_settings()
        service_token = settings.read_ai_service_token()
        if not service_token:
            raise AppError(
                "AI_SERVICE_AUTH_UNCONFIGURED",
                "AI service authentication is unconfigured.",
                503,
            )
        payload = {
            "execution_id": str(execution_id),
            "workspace_id": str(workspace_id),
            "actor_id": str(actor_id),
            "instruction": instruction,
            "model": model,
            "allowed_tools": allowed_tools,
            "safety_identifier": safety_identifier,
        }
        try:
            async with httpx.AsyncClient(timeout=35) as client:
                response = await client.post(
                    f"{settings.ai_service_url.rstrip('/')}/v1/plans",
                    headers={"x-service-token": service_token, "x-request-id": request_id},
                    json=payload,
                )
        except httpx.HTTPError as error:
            raise AppError("AI_SERVICE_UNAVAILABLE", "The AI service is unavailable.", 503) from error

        if not response.is_success:
            try:
                body = response.json()
                provider_error = body["error"]
                code = str(provider_error["code"])
                message = str(provider_error["message"])
            except (ValueError, KeyError, TypeError):
                code, message = "AI_SERVICE_FAILED", "The AI service could not create a plan."
            safe_status = response.status_code if response.status_code in {401, 422, 502, 503} else 502
            raise AppError(code[:80], message[:500], safe_status)
        try:
            return AIPlanResult.model_validate(response.json())
        except (ValueError, ValidationError) as error:
            raise AppError(
                "AI_SERVICE_RESPONSE_INVALID",
                "The AI service returned an invalid response.",
                502,
            ) from error


def get_ai_plan_client() -> PlanClient:
    return AIPlanClient()
