from __future__ import annotations

import uuid
from dataclasses import dataclass
from typing import Any, Literal

from pydantic import BaseModel, Field, ValidationError, field_validator
from redis.exceptions import RedisError
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from .authorization import permissions_for
from .errors import AppError
from .health import AIHealthProbe, RedisClient
from .models import Workspace
from .policy import Risk


class SystemStatusInput(BaseModel):
    services: list[
        Literal["core-api", "database", "redis", "ai", "browser"]
    ] = Field(default_factory=lambda: ["core-api"], min_length=1, max_length=5)

    @field_validator("services")
    @classmethod
    def services_are_unique(cls, value: list[str]) -> list[str]:
        if len(value) != len(set(value)):
            raise ValueError("Services must be unique")
        return value


class SystemStatusOutput(BaseModel):
    statuses: dict[str, Literal["ok", "unavailable", "unconfigured"]]


class WorkspaceProfileInput(BaseModel):
    include_permissions: bool = True


class WorkspaceProfileOutput(BaseModel):
    workspace_name: str
    timezone: str
    permissions: list[str] = Field(default_factory=list)


@dataclass(frozen=True, slots=True)
class CoreToolDefinition:
    name: str
    permission: str
    risk: Risk
    is_write: bool
    timeout_seconds: int
    verification_strategy: str
    input_model: type[BaseModel]
    output_model: type[BaseModel]

    def validate_arguments(self, arguments: dict[str, Any]) -> BaseModel:
        try:
            return self.input_model.model_validate(arguments)
        except ValidationError as error:
            raise AppError(
                "TOOL_ARGUMENTS_INVALID", "The plan proposed invalid tool arguments.", 422
            ) from error


CORE_TOOLS = {
    "system.status.read": CoreToolDefinition(
        name="system.status.read",
        permission="system.status.read",
        risk=Risk.LOW,
        is_write=False,
        timeout_seconds=5,
        verification_strategy="authoritative_health_response",
        input_model=SystemStatusInput,
        output_model=SystemStatusOutput,
    ),
    "workspace.profile.read": CoreToolDefinition(
        name="workspace.profile.read",
        permission="workspace.profile.read",
        risk=Risk.LOW,
        is_write=False,
        timeout_seconds=5,
        verification_strategy="authoritative_database_read",
        input_model=WorkspaceProfileInput,
        output_model=WorkspaceProfileOutput,
    ),
}


def get_core_tool(name: str) -> CoreToolDefinition:
    tool = CORE_TOOLS.get(name)
    if not tool:
        raise AppError("AI_PLAN_TOOL_UNKNOWN", "The plan contained an unknown tool.", 422)
    return tool


@dataclass(slots=True)
class ToolContext:
    session: AsyncSession
    redis: RedisClient
    ai_probe: AIHealthProbe
    workspace_id: uuid.UUID
    user_id: uuid.UUID


async def _system_status(payload: SystemStatusInput, context: ToolContext) -> SystemStatusOutput:
    statuses: dict[str, Literal["ok", "unavailable", "unconfigured"]] = {}
    for service in payload.services:
        if service in {"core-api", "database"}:
            statuses[service] = "ok"
        elif service == "redis":
            try:
                await context.redis.ping()
            except (RedisError, ConnectionError):
                statuses[service] = "unavailable"
            else:
                statuses[service] = "ok"
        elif service == "ai":
            ai_status = await context.ai_probe.status()
            statuses[service] = "ok" if ai_status == "configured" else ai_status
        else:
            statuses[service] = "unconfigured"
    return SystemStatusOutput(statuses=statuses)


async def _workspace_profile(
    payload: WorkspaceProfileInput, context: ToolContext
) -> WorkspaceProfileOutput:
    workspace = await context.session.scalar(
        select(Workspace).where(Workspace.id == context.workspace_id)
    )
    if not workspace:
        raise AppError("WORKSPACE_NOT_FOUND", "The workspace is unavailable.", 404)
    granted = (
        sorted(await permissions_for(
            context.session,
            workspace_id=context.workspace_id,
            user_id=context.user_id,
        ))
        if payload.include_permissions
        else []
    )
    return WorkspaceProfileOutput(
        workspace_name=workspace.name,
        timezone=workspace.timezone,
        permissions=granted,
    )


async def execute_core_tool(
    tool: CoreToolDefinition, arguments: dict[str, Any], context: ToolContext
) -> dict[str, Any]:
    payload = tool.validate_arguments(arguments)
    if tool.name == "system.status.read":
        output = await _system_status(SystemStatusInput.model_validate(payload), context)
    elif tool.name == "workspace.profile.read":
        output = await _workspace_profile(WorkspaceProfileInput.model_validate(payload), context)
    else:  # pragma: no cover - get_core_tool makes this unreachable
        raise AppError("TOOL_IMPLEMENTATION_MISSING", "The tool is unavailable.", 503)
    try:
        validated = tool.output_model.model_validate(output)
    except ValidationError as error:  # pragma: no cover - defensive provider boundary
        raise AppError("TOOL_OUTPUT_INVALID", "The tool returned invalid output.", 502) from error
    return validated.model_dump(mode="json")
