from functools import lru_cache
import logging
from typing import Annotated
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

from fastapi import APIRouter, Depends, HTTPException
from sqlmodel import select

from maur.models import CodingTask, SessionDep
from maur.settings import MaurSettings
from maur.schemas import (
    ExceptionPayload,
    ManualTaskPayload,
    TaskResponse,
)
from maur.components import coding_tasks_pubsub, CodingTaskMessage


logger = logging.getLogger(__name__)

router = APIRouter()


bearer = OAuth2PasswordBearer(tokenUrl="/token")




@lru_cache
def maur_settings() -> MaurSettings:
    return MaurSettings()  # type: ignore


SettingsDep = Annotated[MaurSettings, Depends(maur_settings)]


async def raise_on_unauthenticated(
    token: Annotated[str, Depends(bearer)]
) -> None:
    settings = maur_settings()

    if token != settings.maur_bearer_token.get_secret_value():
        raise HTTPException(status_code=403)


async def _dedup_check(session: SessionDep, source_id: str) -> CodingTask | None:
    result = await session.exec(
        select(CodingTask)
        .where(CodingTask.source_id == source_id)
        .where(CodingTask.finnished_at == None)  # type: ignore[union-attr]
        .where(CodingTask.error == None)  # type: ignore[union-attr]
    )
    return result.first()


@router.post("/token")
async def login(form_data: Annotated[OAuth2PasswordRequestForm, Depends()]):

    settings = maur_settings()

    if form_data.password != settings.maur_bearer_token.get_secret_value():
        raise HTTPException(status_code=400)

    return { "access_token": form_data.password }


@router.post(
    "/webhooks/exception", 
    dependencies=[Depends(raise_on_unauthenticated)]
)
async def exception_webhook(
    payload: ExceptionPayload,
    session: SessionDep,
) -> TaskResponse:

    existing = await _dedup_check(session, payload.fingerprint)
    if existing:
        raise HTTPException(
            status_code=409,
            detail={"message": "Task already in progress", "task_id": str(existing.id)},
        )

    prompt = (
        f"Fix the following exception:\n\nTitle: {payload.title}\n\n"
        f"Stack trace / details:\n{payload.description}"
    )
    if payload.extra:
        prompt += f"\n\nExtra context:\n{payload.extra}"

    task = CodingTask(
        source="exception",
        source_id=payload.fingerprint,
        repo_branch=payload.repo_branch,
        prompt=prompt,
    )
    await task.insert(session)
    await coding_tasks_pubsub.publish(CodingTaskMessage(task_id=task.id))

    return TaskResponse(task_id=task.id, status="pending", message="Task queued")

@router.post(
    "/tasks",
    dependencies=[Depends(raise_on_unauthenticated)]
)
async def create_manual_task(
    payload: ManualTaskPayload,
    session: SessionDep,
) -> TaskResponse:

    existing = await _dedup_check(session, payload.source_id)
    if existing:
        raise HTTPException(
            status_code=409,
            detail={"message": "Task already in progress", "task_id": str(existing.id)},
        )

    task = CodingTask(
        source="manual",
        source_id=payload.source_id,
        repo_branch=payload.repo_branch,
        prompt=payload.prompt,
    )
    await task.insert(session)
    await coding_tasks_pubsub.publish(CodingTaskMessage(task_id=task.id))

    return TaskResponse(task_id=task.id, status="pending", message="Task queued")


@router.get(
    "/tasks",
    dependencies=[Depends(raise_on_unauthenticated)]
)
async def list_tasks(
    session: SessionDep,
) -> list[CodingTask]:
    result = await session.exec(select(CodingTask).order_by(CodingTask.created_at.desc()).limit(50))  # type: ignore[union-attr]
    return list(result.all())


@router.get(
    "/tasks/{task_id}",
    dependencies=[Depends(raise_on_unauthenticated)],
)
async def get_task(
    task_id: str,
    session: SessionDep,
) -> CodingTask:
    import uuid as uuid_mod

    task = await CodingTask.get(uuid_mod.UUID(task_id), session)
    if task is None:
        raise HTTPException(status_code=404, detail="Task not found")
    return task
