import re
import secrets
import os
import time
import json
import asyncio
from fastapi import APIRouter, Request, Depends, Form
from fastapi.responses import HTMLResponse, RedirectResponse, Response, StreamingResponse
from fastapi.templating import Jinja2Templates
from fastapi.security import HTTPBasic, HTTPBasicCredentials
from itsdangerous import TimestampSigner, SignatureExpired, BadSignature
from pydantic import BaseModel
from .broadcaster import subscribe, unsubscribe
from .devops import (
    check_iteration_exists,
    get_work_item,
    find_existing_task,
    create_task,
    iteration_name,
)
from .zammad import get_ticket, get_ticket_articles, get_customer_info
from .ai import suggest_tags, _has_provider as ai_enabled
from .config import settings

router = APIRouter()
templates = Jinja2Templates(directory="app/templates")

# Used as ?v= query string on static assets to bust browser cache on every deploy.
_STATIC_VER = int(time.time())

_SESSION_COOKIE = "za_session"
_basic = HTTPBasic(auto_error=False)  # shows "Authorize" button in Swagger UI
_session_secret = settings.session_secret or os.urandom(32).hex()
_signer = TimestampSigner(_session_secret)


def _valid_session(request: Request) -> bool:
    token = request.cookies.get(_SESSION_COOKIE)
    if not token:
        return False
    try:
        _signer.unsign(token, max_age=settings.session_timeout)
        return True
    except (SignatureExpired, BadSignature):
        return False


def _set_session(response: Response) -> None:
    token = _signer.sign(b"ok").decode()
    response.set_cookie(
        _SESSION_COOKIE, token,
        max_age=settings.session_timeout,
        httponly=True,
        samesite="lax",
    )


def _check_basic(credentials: HTTPBasicCredentials | None) -> bool:
    """Validate HTTP Basic credentials (used by Swagger UI Authorize)."""
    if not credentials:
        return False
    ok_user = secrets.compare_digest(credentials.username.encode(), settings.web_username.encode())
    ok_pass = secrets.compare_digest(credentials.password.encode(), settings.web_password.encode())
    return ok_user and ok_pass


def _require_auth(request: Request):
    if not settings.web_password:
        return
    if not _valid_session(request):
        raise _redirect_login()


def _redirect_login():
    return RedirectResponse(url="/login", status_code=302)


# ── Auth routes ────────────────────────────────────────────────────────────────

@router.get("/login", response_class=HTMLResponse)
async def login_page(request: Request, error: str = ""):
    return templates.TemplateResponse(
        request=request, name="login.html", context={"error": error, "static_ver": _STATIC_VER}
    )


@router.post("/login")
async def login_post(
    request: Request,
    username: str = Form(...),
    password: str = Form(...),
):
    ok_user = secrets.compare_digest(username.encode(), settings.web_username.encode())
    ok_pass = secrets.compare_digest(password.encode(), settings.web_password.encode())

    if settings.web_password and not (ok_user and ok_pass):
        return templates.TemplateResponse(
            request=request,
            name="login.html",
            context={"error": "Invalid username or password."},
            status_code=401,
        )

    response = RedirectResponse(url="/", status_code=302)
    _set_session(response)
    return response


@router.get("/logout")
async def logout():
    response = RedirectResponse(url="/login", status_code=302)
    response.delete_cookie(_SESSION_COOKIE)
    return response


# ── SSE notifications ──────────────────────────────────────────────────────────

@router.get("/events")
async def sse_events(request: Request):
    if not settings.notifications_enabled:
        return Response(status_code=404)
    if settings.web_password and not _valid_session(request):
        return Response(status_code=401)  # JS EventSource onerror → redirect to /login

    async def stream():
        q = subscribe()
        try:
            yield "retry: 3000\n\n"  # auto-reconnect every 3s
            while True:
                if await request.is_disconnected():
                    break
                try:
                    event = await asyncio.wait_for(q.get(), timeout=25)
                    yield f"data: {json.dumps(event)}\n\n"
                except asyncio.TimeoutError:
                    yield ": keepalive\n\n"  # prevent proxy timeout
        finally:
            unsubscribe(q)

    return StreamingResponse(stream(), media_type="text/event-stream",
                             headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"})


# ── App routes ─────────────────────────────────────────────────────────────────

class CreateTaskRequest(BaseModel):
    ticket_number: str
    ticket_title: str
    ticket_url: str
    iteration_name: str
    story_id: int
    activity: str = ""
    original_estimate: float | None = None
    remaining_work: float | None = None
    expert_estimate: float | None = None
    tags: str = ""


@router.get("/", response_class=HTMLResponse)
async def index(request: Request):
    if settings.web_password and not _valid_session(request):
        return RedirectResponse(url="/login", status_code=302)
    return templates.TemplateResponse(
        request=request,
        name="index.html",
        context={
            "default_iteration": iteration_name(),
            "notifications_enabled": settings.notifications_enabled,
            "devops_enabled": settings.devops_enabled,
            "static_ver": _STATIC_VER,
        },
    )


@router.get("/web/ticket-info")
async def ticket_info(request: Request, url: str, credentials: HTTPBasicCredentials | None = Depends(_basic)):
    if settings.web_password and not _valid_session(request) and not _check_basic(credentials):
        return Response(content='{"status":"error","message":"Session expired. Please log in again."}',
                        media_type="application/json", status_code=401)
    url = url.replace('%23', '#')
    m = re.search(r'[/#]ticket/zoom/(\d+)', url)
    if not m:
        return {"status": "error", "message": "Cannot parse ticket ID from URL. Expected format: .../#ticket/zoom/12345"}
    ticket_id = int(m.group(1))
    try:
        ticket = await get_ticket(ticket_id)
        state_id    = ticket.get("state_id")
        priority_id = ticket.get("priority_id")
        state_map    = {1: "new", 2: "open", 3: "pending reminder", 4: "closed", 7: "pending close"}
        priority_map = {1: "low", 2: "normal", 3: "high"}
        state    = state_map.get(state_id, "unknown")
        priority = priority_map.get(priority_id, "")

        return {
            "status": "ok",
            "ticket_id": ticket_id,
            "ticket_number": str(ticket["number"]),
            "ticket_title": ticket.get("title", ""),
            "ticket_state": state,
            "ticket_priority": priority,
        }
    except Exception as e:
        return {"status": "error", "message": f"Zammad error: {e}"}


@router.get("/web/suggest-tags")
async def web_suggest_tags(
    request: Request,
    ticket_id: int,
    credentials: HTTPBasicCredentials | None = Depends(_basic),
):
    if settings.web_password and not _valid_session(request) and not _check_basic(credentials):
        return Response(content='{"status":"error","message":"Session expired. Please log in again."}',
                        media_type="application/json", status_code=401)
    if not ai_enabled():
        return {"status": "error", "message": "AI provider not configured"}

    try:
        ticket, articles = await asyncio.gather(
            get_ticket(ticket_id),
            get_ticket_articles(ticket_id),
        )
        customer_id = ticket.get("customer_id")
        customer = await get_customer_info(customer_id) if customer_id else {}

        article_bodies = [a.get("body", "") for a in articles if not a.get("internal", False)]

        ai_tags_str = await suggest_tags(
            ticket_title=ticket.get("title", ""),
            ticket_body_html=ticket.get("note", "") or (article_bodies[0] if article_bodies else ""),
            customer_email=customer.get("email", ""),
            customer_org=customer.get("organization", ""),
            customer_name=customer.get("name", ""),
            articles=article_bodies[1:],  # first article is the description; rest are replies
        )
    except Exception as e:
        return {"status": "error", "message": f"Failed to fetch ticket data: {e}"}

    tags = [t.strip() for t in ai_tags_str.split(";") if t.strip()]
    return {"status": "ok", "tags": tags}


@router.post("/web/create-task")
async def web_create_task(request: Request, payload: CreateTaskRequest, credentials: HTTPBasicCredentials | None = Depends(_basic)):
    if settings.web_password and not _valid_session(request) and not _check_basic(credentials):
        return Response(content='{"status":"error","message":"Session expired. Please log in again."}',
                        media_type="application/json", status_code=401)

    if not settings.devops_enabled:
        return {"status": "error", "message": "DevOps integration is disabled (DEVOPS_ENABLED=false)"}

    iter_path = f"{settings.devops_project}\\{payload.iteration_name}"

    if not await check_iteration_exists(payload.iteration_name):
        return {"status": "error", "message": f"Iteration '{payload.iteration_name}' not found in Azure DevOps"}

    story = await get_work_item(payload.story_id)
    if story is None:
        return {"status": "error", "message": f"User Story #{payload.story_id} not found"}
    if story["fields"].get("System.WorkItemType") != "User Story":
        return {"status": "error", "message": f"Work item #{payload.story_id} is not a User Story (type: {story['fields'].get('System.WorkItemType')})"}

    existing = await find_existing_task(payload.ticket_number)
    if existing:
        return {
            "status": "exists",
            "message": f"Task #{existing['id']} already exists for Ticket #{payload.ticket_number}",
            "task_id": existing["id"],
            "task_url": existing["url"],
        }

    task = await create_task(
        story_id=payload.story_id,
        ticket_id=0,
        ticket_number=payload.ticket_number,
        ticket_title=payload.ticket_title,
        ticket_url=payload.ticket_url,
        iter_path=iter_path,
        activity=payload.activity or "Development",
        original_estimate=payload.original_estimate if payload.original_estimate is not None else 0.5,
        remaining_work=payload.remaining_work if payload.remaining_work is not None else 0.5,
        expert_estimate=payload.expert_estimate if payload.expert_estimate is not None else 0.25,
        tags=payload.tags,
    )
    return {
        "status": "created",
        "story_id": payload.story_id,
        "task_id": task["id"],
        "task_url": task["url"],
    }
