import hashlib
import hmac
import time
from contextlib import suppress
from functools import lru_cache

from fastapi import APIRouter, Header, HTTPException, Request

from maur.components import worker_tasks
from maur.models import CodingTask
from maur.repos import CodingTaskRepoDep, DuplicateTaskError
from maur.slack.schemas import SlackWebhookPayload
from maur.slack.settings import SlackSettings
from maur.worker import CodingTaskMessage


router = APIRouter()


@lru_cache
def slack_settings() -> SlackSettings:
    return SlackSettings.from_env()


def _verify_signature(secret: str, timestamp: str, body: bytes, signature: str) -> bool:
    base = f"v0:{timestamp}:{body.decode()}"
    expected = "v0=" + hmac.new(secret.encode(), base.encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)


@router.post("/webhook")
async def slack_webhook(
    request: Request,
    tasks: CodingTaskRepoDep,
    x_slack_signature: str = Header(),
    x_slack_request_timestamp: str = Header(),
) -> dict:
    body = await request.body()
    settings = slack_settings()

    # Reject stale requests (replay attack prevention)
    with suppress(ValueError):
        if abs(time.time() - int(x_slack_request_timestamp)) > 300:
            raise HTTPException(status_code=403, detail="Request too old")

    if not _verify_signature(
        settings.slack_signing_secret.get_secret_value(),
        x_slack_request_timestamp,
        body,
        x_slack_signature,
    ):
        raise HTTPException(status_code=403, detail="Invalid signature")

    payload = SlackWebhookPayload.model_validate_json(body)

    # Respond to Slack's URL verification challenge
    if payload.type == "url_verification":
        return {"challenge": payload.challenge}

    if payload.type != "event_callback" or payload.event is None:
        return {"ok": True}

    event = payload.event

    # Ignore bot messages to avoid loops
    if payload.is_bot_message:
        return {"ok": True}

    # Ignore non-mention/message events
    if event.type not in ("app_mention", "message"):
        return {"ok": True}

    if settings.channel_filter and event.channel != settings.channel_filter:
        return {"ok": True}

    prompt = settings.prompt_template.format(
        channel=event.channel,
        text=event.text,
    )

    with suppress(DuplicateTaskError):
        task = await tasks.insert(
            CodingTask(
                source="slack",
                source_id=f"{event.channel}:{event.ts}",
                repo_branch=settings.repo_branch,
                prompt=prompt,
            )
        )
        await worker_tasks.publish(CodingTaskMessage(task_id=task.id))

    return {"ok": True}
