from functools import lru_cache
import logging
import secrets
import uuid as uuid_mod
from datetime import datetime, timezone
from typing import Annotated
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm

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

from maur.models import ApiToken, CodingTask, SessionDep
from maur.settings import MaurSettings
from maur.schemas import (
    ApiTokenResponse,
    CreateTokenPayload,
    ExceptionPayload,
    ManualTaskPayload,
    TaskResponse,
)
from maur.components import worker_tasks, CodingTaskMessage


logger = logging.getLogger(__name__)

router = APIRouter()


bearer = OAuth2PasswordBearer(tokenUrl="/login")


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


SettingsDep = Annotated[MaurSettings, Depends(maur_settings)]


async def raise_on_not_admin(
    token: Annotated[str, Depends(bearer)],
) -> None:
    if token != maur_settings().maur_admin_token.get_secret_value():
        raise HTTPException(status_code=403)


async def raise_on_unauthenticated(
    token: Annotated[str, Depends(bearer)],
    session: SessionDep,
) -> None:
    if token == maur_settings().maur_admin_token.get_secret_value():
        return
    result = await session.exec(select(ApiToken).where(ApiToken.token == token))
    api_token = result.first()
    if api_token is None or api_token.revoked_at is not None or api_token.deleted_at is not None:
        raise HTTPException(status_code=403)
    if api_token.expires_at and api_token.expires_at < datetime.now(tz=timezone.utc):
        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("/login")
async def login(
    form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
    session: SessionDep,
):
    settings = maur_settings()

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

    new_token = ApiToken(
        token=secrets.token_urlsafe(32),
        name=form_data.username or "default",
    )
    await new_token.insert(session)
    return {"access_token": new_token.token, "token_type": "bearer"}


@router.get("/tokens", dependencies=[Depends(raise_on_not_admin)])
async def list_tokens(session: SessionDep) -> list[ApiTokenResponse]:
    result = await session.exec(
        select(ApiToken).where(ApiToken.deleted_at == None).order_by(ApiToken.created_at.desc())  # type: ignore[union-attr]
    )
    return [
        ApiTokenResponse(
            id=t.id,
            token="",
            name=t.name,
            description=t.description,
            metadata=t.metadata_,
            created_at=t.created_at,
            updated_at=t.updated_at,
            expires_at=t.expires_at,
            revoked_at=t.revoked_at,
        )
        for t in result.all()
    ]


@router.post("/tokens", dependencies=[Depends(raise_on_not_admin)])
async def create_token(payload: CreateTokenPayload, session: SessionDep) -> ApiTokenResponse:
    new_token = ApiToken(
        token=secrets.token_urlsafe(32),
        name=payload.name,
        description=payload.description,
        metadata_=payload.metadata,
        expires_at=payload.expires_at,
    )
    await new_token.insert(session)
    return ApiTokenResponse(
        id=new_token.id,
        token=new_token.token,
        name=new_token.name,
        description=new_token.description,
        metadata=new_token.metadata_,
        created_at=new_token.created_at,
        updated_at=new_token.updated_at,
        expires_at=new_token.expires_at,
        revoked_at=new_token.revoked_at,
    )


@router.delete("/tokens/{token_id}", dependencies=[Depends(raise_on_not_admin)])
async def revoke_token(token_id: str, session: SessionDep) -> ApiTokenResponse:
    api_token = await ApiToken.get(uuid_mod.UUID(token_id), session)
    if api_token is None:
        raise HTTPException(status_code=404, detail="Token not found")
    api_token.revoked_at = datetime.now(tz=timezone.utc)
    await api_token.update(session)
    return ApiTokenResponse(
        id=api_token.id,
        token="",
        name=api_token.name,
        description=api_token.description,
        metadata=api_token.metadata_,
        created_at=api_token.created_at,
        updated_at=api_token.updated_at,
        expires_at=api_token.expires_at,
        revoked_at=api_token.revoked_at,
    )


@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 worker_tasks.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 worker_tasks.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:
    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
