"""
FastAPI application entry point.
Handles webhook ingestion from Zammad and orchestrates:
  1. New-ticket guard (skip if already has replies)
  2. AI-generated acknowledgment comment posted back to Zammad
  3. Azure DevOps task creation under the current monthly User Story
"""

import asyncio
import hashlib
import hmac
import json
import logging
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import Any
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from .config import settings
from .devops import ensure_and_create
from .zammad import post_comment, get_ticket, get_ticket_articles, get_customer_name, get_agent_name
from .ai import generate_acknowledgment, classify_activity, suggest_tags, _has_provider as ai_enabled
from .broadcaster import publish
from .web import router as web_router
from .logging_config import setup_logging

setup_logging()
log = logging.getLogger(__name__)

limiter = Limiter(key_func=get_remote_address)
app = FastAPI(title="Zammad Automation", debug=settings.debug)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.mount("/static", StaticFiles(directory="app/static"), name="static")
app.include_router(web_router)


class WebhookPayload(BaseModel):
    """Expected body shape from Zammad webhook trigger."""
    ticket: dict[str, Any]


# Build allowlist once at startup; empty set = allow all IPs
_allowed_ips: set[str] = {
    ip.strip() for ip in settings.webhook_allowed_ips.split(",") if ip.strip()
}


def _verify_signature(body: bytes, sig_header: str) -> bool:
    """Validate HMAC-SHA1 signature sent by Zammad (X-Hub-Signature header).
    Returns True unconditionally when WEBHOOK_SECRET is not set."""
    if not settings.webhook_secret:
        return True
    expected = "sha1=" + hmac.new(
        settings.webhook_secret.encode(), body, hashlib.sha1
    ).hexdigest()
    return hmac.compare_digest(expected, sig_header)


def _verify_ip(request: Request) -> None:
    """Block request if client IP is not in WEBHOOK_ALLOWED_IPS.
    Reads X-Forwarded-For first (set by Traefik/nginx) to get real client IP."""
    if not _allowed_ips:
        return
    forwarded = request.headers.get("X-Forwarded-For", "").split(",")
    client_ip = forwarded[0].strip() if forwarded[0].strip() else (request.client.host if request.client else "")
    if client_ip not in _allowed_ips:
        log.warning("Webhook blocked: IP %s not in allowlist", client_ip)
        raise HTTPException(status_code=403, detail="Forbidden")


async def _process_ticket(ticket_id: int) -> None:
    """Background task: process a newly created Zammad ticket.

    Steps:
      1. Fetch ticket metadata + articles
      2. Guard: skip if ticket already has >1 article (already processed / updated)
      3. Send AI-generated acknowledgment comment to Zammad
      4. Create Azure DevOps task under current monthly User Story (if enabled)

    All steps emit SSE progress events visible in the web UI.
    """
    steps: list[dict] = []

    def _step(label: str, ok: bool, detail: str = "") -> None:
        steps.append({"label": label, "ok": ok, "detail": detail})

    try:
        ticket = await get_ticket(ticket_id)
        number = str(ticket["number"])
        title = ticket.get("title", "")
        ticket_url = f"{settings.zammad_url}/#ticket/zoom/{ticket_id}"
        customer_name = await get_customer_name(ticket.get("customer_id", 0))
        agent_name = await get_agent_name()

        def _publish_progress(extra: dict = {}):
            publish({"type": "progress", "ticket_number": number,
                     "ticket_title": title, "ticket_url": ticket_url,
                     "steps": steps, **extra})

        # 1. Fetch articles
        body_html = ""
        try:
            articles = await get_ticket_articles(ticket_id)
            body_html = articles[0].get("body", "") if articles else ""
            _step("Fetch ticket articles", True)
        except Exception:
            log.exception("Ticket #%s → failed to fetch articles", number)
            _step("Fetch ticket articles", False, "Could not retrieve articles")
            _publish_progress({"status": "error"})
            return

        # Guard: only process tickets with exactly 1 article (customer's opening message).
        # More than 1 means the ticket was updated or already has a reply — skip.
        if len(articles) > 1:
            log.info("Ticket #%s → %d articles found, not a new ticket — skipping", number, len(articles))
            _step("New ticket check", False, f"{len(articles)} articles exist — ticket already processed")
            _publish_progress({"status": "skipped"})
            return

        _step("New ticket check", True, "Only 1 article — new ticket")

        # 2. AI acknowledgment
        if ai_enabled():
            try:
                ack = await generate_acknowledgment(title, body_html, customer_name, agent_name)
                ack_html = ack.replace("\n", "<br>")
                await post_comment(ticket_id, ack_html)
                log.info("Ticket #%s → AI acknowledgment sent", number)
                _step("AI acknowledgment", True, "Sent to Zammad")
            except Exception as e:
                log.exception("Ticket #%s → AI acknowledgment failed", number)
                _step("AI acknowledgment", False, str(e)[:80])
        else:
            _step("AI acknowledgment", False, "Skipped — no API key configured")

        # 3. DevOps task creation
        if not settings.devops_enabled:
            log.info("Ticket #%s → DevOps disabled, skipping", number)
            _step("DevOps task", False, "Disabled (DEVOPS_ENABLED=false)")
            _publish_progress({"status": "done"})
            return

        # AI classifies activity type and suggests tags for the DevOps task
        activity, tags = await asyncio.gather(
            classify_activity(title, body_html),
            suggest_tags(title, body_html),
        )
        log.info("Ticket #%s → activity='%s' tags='%s'", number, activity, tags)

        result = await ensure_and_create(
            ticket_id=ticket_id,
            ticket_number=number,
            ticket_title=title,
            ticket_url=ticket_url,
            activity=activity,
            tags=tags,
        )

        if result["created"]:
            log.info("Ticket #%s → task %s created", number, result["task_id"])
            _step("DevOps task", True, f"Task #{result['task_id']} created")
        else:
            log.info("Ticket #%s → task %s already existed", number, result["task_id"])
            _step("DevOps task", False, f"Task #{result['task_id']} already exists")

        _publish_progress({
            "status": "created" if result["created"] else "exists",
            "task_id": result["task_id"],
            "task_url": result["task_url"],
        })

    except Exception:
        log.exception("Failed to process ticket %s", ticket_id)
        steps.append({"label": "Processing", "ok": False, "detail": "Unexpected error — check logs"})
        publish({"type": "progress", "ticket_number": str(ticket_id),
                 "ticket_title": "", "ticket_url": "", "steps": steps, "status": "error"})


@app.post("/webhook/ticket-created", summary="Zammad webhook receiver")
@limiter.limit(settings.webhook_rate_limit)
async def ticket_created(request: Request, background_tasks: BackgroundTasks, payload: WebhookPayload):
    """Receive ticket-created webhook from Zammad.
    Validates IP, HMAC signature, then queues background processing."""
    _verify_ip(request)

    body = await request.body()

    if settings.debug:
        log.debug("Webhook headers: %s", dict(request.headers))
        log.debug("Webhook body: %s", body.decode(errors="replace"))

    sig = request.headers.get("X-Hub-Signature", "")
    if not _verify_signature(body, sig):
        raise HTTPException(status_code=401, detail="Invalid signature")

    ticket_id = payload.ticket.get("id")
    if not ticket_id:
        raise HTTPException(status_code=400, detail="No ticket.id in payload")

    publish({"type": "received", "ticket_id": ticket_id})
    background_tasks.add_task(_process_ticket, ticket_id)
    return {"status": "accepted", "ticket_id": ticket_id}


@app.get("/health", summary="Health check")
async def health():
    """Simple liveness probe for load balancers and monitoring."""
    return {"status": "ok"}
