from typing import Annotated, Protocol

import httpx
from fastapi import APIRouter, Depends
from fastapi.responses import JSONResponse
from redis.asyncio import Redis
from redis.exceptions import RedisError
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession

from .config import get_settings
from .database import get_session

router = APIRouter(prefix="/health", tags=["health"])
SessionDep = Annotated[AsyncSession, Depends(get_session)]


class RedisClient(Protocol):
    async def ping(self): ...
    async def eval(self, script: str, numkeys: int, *keys_and_args): ...
    async def delete(self, *names: str): ...
    async def aclose(self): ...


async def get_redis_client():
    client = Redis.from_url(get_settings().redis_url, decode_responses=True)
    try:
        yield client
    finally:
        await client.aclose()


RedisDep = Annotated[RedisClient, Depends(get_redis_client)]


class AIHealthProbe(Protocol):
    async def status(self) -> str: ...


class HttpAIHealthProbe:
    async def status(self) -> str:
        settings = get_settings()
        token = settings.read_ai_service_token()
        if not token:
            return "unconfigured"
        try:
            async with httpx.AsyncClient(timeout=3) as client:
                response = await client.get(
                    f"{settings.ai_service_url.rstrip('/')}/health",
                    headers={"x-service-token": token},
                )
            payload = response.json()
        except (httpx.HTTPError, ValueError, KeyError, TypeError):
            return "unavailable"
        if response.status_code != 200 or payload.get("service") != "ai-agent":
            return "unavailable"
        provider = payload.get("provider")
        return provider if provider in {"configured", "unconfigured"} else "unavailable"


def get_ai_health_probe() -> AIHealthProbe:
    return HttpAIHealthProbe()


AIHealthDep = Annotated[AIHealthProbe, Depends(get_ai_health_probe)]


class ComputerHealthProbe(Protocol):
    async def status(self) -> str: ...


class HttpComputerHealthProbe:
    async def status(self) -> str:
        settings = get_settings()
        if (
            not settings.read_computer_service_token()
            or not settings.read_computer_capability_private_key()
        ):
            return "unconfigured"
        try:
            async with httpx.AsyncClient(timeout=3) as client:
                response = await client.get(
                    f"{settings.computer_service_url.rstrip('/')}/health",
                    headers={"x-service-token": settings.read_computer_service_token()},
                )
            payload = response.json()
        except (httpx.HTTPError, ValueError, KeyError, TypeError):
            return "unavailable"
        if response.status_code != 200 or payload.get("service") != "computer-agent":
            return "unavailable"
        browser = payload.get("browser")
        return browser if browser in {"configured", "unconfigured"} else "unavailable"


def get_computer_health_probe() -> ComputerHealthProbe:
    return HttpComputerHealthProbe()


ComputerHealthDep = Annotated[ComputerHealthProbe, Depends(get_computer_health_probe)]


@router.get("/database")
async def database_health(session: SessionDep):
    try:
        await session.execute(text("SELECT 1"))
    except SQLAlchemyError:
        return JSONResponse(status_code=503, content={"status": "unavailable", "service": "database"})
    return {"status": "ok", "service": "database"}


@router.get("/redis")
async def redis_health(client: RedisDep):
    try:
        await client.ping()
    except (RedisError, ConnectionError):
        return JSONResponse(status_code=503, content={"status": "unavailable", "service": "redis"})
    return {"status": "ok", "service": "redis"}


@router.get("/readiness")
async def readiness(
    session: SessionDep,
    client: RedisDep,
    ai_probe: AIHealthDep,
    computer_probe: ComputerHealthDep,
):
    try:
        await session.execute(text("SELECT 1"))
        await client.ping()
    except (SQLAlchemyError, RedisError, ConnectionError):
        return JSONResponse(
            status_code=503, content={"status": "unavailable", "service": "core-api"}
        )
    settings = get_settings()
    if settings.production_configuration_errors():
        return JSONResponse(
            status_code=503, content={"status": "unconfigured", "service": "core-api"}
        )
    if settings.app_env == "production":
        ai_status, computer_status = await ai_probe.status(), await computer_probe.status()
        if ai_status != "configured" or computer_status != "configured":
            return JSONResponse(
                status_code=503, content={"status": "unavailable", "service": "core-api"}
            )
    return {"status": "ready", "service": "core-api"}


@router.get("/ai")
async def ai_health(probe: AIHealthDep):
    status = await probe.status()
    if status == "unavailable":
        return JSONResponse(status_code=503, content={"status": status, "service": "ai"})
    return {"status": status, "service": "ai"}


@router.get("/browser")
async def browser_health(probe: ComputerHealthDep):
    status = await probe.status()
    if status == "unavailable":
        return JSONResponse(status_code=503, content={"status": status, "service": "browser"})
    return {"status": status, "service": "browser"}
