"""API endpoints for Zapier / Make.com automation integration.

Provides a REST hooks subscription interface for outgoing triggers and
incoming action endpoints that external automation platforms can call.

Outgoing triggers:
    External platforms subscribe to DocuElevate events via
    ``POST /api/automation/hooks/subscribe``.  When a subscribed event
    fires, DocuElevate POSTs a flat Zapier-compatible JSON payload to the
    registered ``target_url``.

Incoming actions:
    ``POST /api/automation/actions/upload`` allows automation platforms to
    push documents into DocuElevate for processing.

Authentication:
    All endpoints require a valid API token via ``Authorization: Bearer``
    header.
"""

import json
import logging
import os
import tempfile
from typing import Annotated, Any

from fastapi import APIRouter, Depends, File, HTTPException, Request, UploadFile, status
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from app.config import settings
from app.database import get_db
from app.models import AutomationHook
from app.utils.automation_hooks import SAMPLE_PAYLOADS
from app.utils.webhook import VALID_EVENTS

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/automation", tags=["automation"])

DbSession = Annotated[Session, Depends(get_db)]


# ---------------------------------------------------------------------------
# Auth helper – require a valid API token (Bearer)
# ---------------------------------------------------------------------------


def _require_api_user(request: Request) -> dict:
    """Ensure the caller is authenticated via session or API token.

    Raises:
        HTTPException: 401 if not authenticated, 403 if automation hooks are disabled.
    """
    if not settings.automation_hooks_enabled:
        raise HTTPException(
            status_code=status.HTTP_403_FORBIDDEN,
            detail="Automation hooks are disabled",
        )

    # Check for API-token user first (set by auth middleware)
    user = getattr(request.state, "api_token_user", None)
    if user:
        return user

    # Fall back to session user
    user = request.session.get("user")
    if user:
        return user

    raise HTTPException(
        status_code=status.HTTP_401_UNAUTHORIZED,
        detail="Authentication required (Bearer token or session)",
    )


AuthUser = Annotated[dict, Depends(_require_api_user)]


# ---------------------------------------------------------------------------
# Pydantic schemas
# ---------------------------------------------------------------------------


class HookSubscribe(BaseModel):
    """Schema for subscribing to automation hook events."""

    target_url: str = Field(..., min_length=1, max_length=2048, description="URL to POST event payloads to")
    events: list[str] = Field(..., min_length=1, description="Event types to subscribe to")
    secret: str | None = Field(default=None, max_length=512, description="Optional HMAC-SHA256 signing secret")
    hook_type: str = Field(
        default="generic",
        max_length=50,
        description="Platform identifier (zapier, make, generic)",
    )
    description: str | None = Field(default=None, max_length=500, description="Optional human-readable label")


class HookResponse(BaseModel):
    """Schema returned when listing or creating hooks."""

    id: int
    target_url: str
    events: list[str]
    is_active: bool
    hook_type: str
    description: str | None
    has_secret: bool

    model_config = {"from_attributes": True}


class ActionUploadResponse(BaseModel):
    """Response after an automation action uploads a document."""

    status: str
    filename: str
    task_id: str | None = None


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------


def _validate_events(events: list[str]) -> None:
    """Raise 422 if any event name is not recognised."""
    invalid = set(events) - VALID_EVENTS
    if invalid:
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Invalid event(s): {', '.join(sorted(invalid))}. Valid: {', '.join(sorted(VALID_EVENTS))}",
        )


def _hook_to_response(hook: AutomationHook) -> dict[str, Any]:
    """Convert a DB model instance to a response dict."""
    try:
        events = json.loads(hook.events)
    except (json.JSONDecodeError, TypeError):
        events = []
    return {
        "id": hook.id,
        "target_url": hook.target_url,
        "events": events,
        "is_active": hook.is_active,
        "hook_type": hook.hook_type,
        "description": hook.description,
        "has_secret": hook.secret is not None and len(hook.secret) > 0,
    }


# ---------------------------------------------------------------------------
# Outgoing triggers – REST hooks subscription endpoints
# ---------------------------------------------------------------------------


@router.post(
    "/hooks/subscribe",
    status_code=status.HTTP_201_CREATED,
    summary="Subscribe to automation events (REST hooks)",
)
def subscribe_hook(body: HookSubscribe, db: DbSession, user: AuthUser) -> dict[str, Any]:
    """Register a new automation hook subscription.

    Zapier and Make.com call this endpoint to subscribe to DocuElevate
    events.  When an event fires, a flat JSON payload is POSTed to
    ``target_url``.
    """
    _validate_events(body.events)

    hook = AutomationHook(
        target_url=body.target_url,
        secret=body.secret,
        events=json.dumps(sorted(body.events)),
        is_active=True,
        hook_type=body.hook_type or "generic",
        description=body.description,
    )
    try:
        db.add(hook)
        db.commit()
        db.refresh(hook)
    except Exception:
        db.rollback()
        raise

    logger.info("Automation hook %d created (type=%s) for events %s", hook.id, hook.hook_type, body.events)
    return _hook_to_response(hook)


@router.delete(
    "/hooks/{hook_id}",
    status_code=status.HTTP_204_NO_CONTENT,
    summary="Unsubscribe an automation hook",
)
def unsubscribe_hook(hook_id: int, db: DbSession, user: AuthUser) -> None:
    """Remove an automation hook subscription.

    Zapier calls this endpoint when a Zap is turned off or deleted.
    """
    hook = db.query(AutomationHook).filter(AutomationHook.id == hook_id).first()
    if not hook:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Hook not found")

    try:
        db.delete(hook)
        db.commit()
    except Exception:
        db.rollback()
        raise

    logger.info("Automation hook %d deleted", hook_id)


@router.get("/hooks", summary="List automation hook subscriptions")
def list_hooks(db: DbSession, user: AuthUser) -> list[dict[str, Any]]:
    """Return all active automation hook subscriptions."""
    hooks = db.query(AutomationHook).order_by(AutomationHook.id).all()
    return [_hook_to_response(h) for h in hooks]


# ---------------------------------------------------------------------------
# Outgoing triggers – sample data for Zapier field mapping
# ---------------------------------------------------------------------------


@router.get("/triggers/sample/{event}", summary="Get sample trigger data")
def get_trigger_sample(event: str, user: AuthUser) -> list[dict[str, Any]]:
    """Return sample payload data for the given event type.

    Zapier uses this during Zap setup to discover available fields and
    provide a mapping interface.  The response is wrapped in an array
    as Zapier expects.
    """
    if event not in VALID_EVENTS:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Unknown event: {event}. Valid: {', '.join(sorted(VALID_EVENTS))}",
        )

    sample = SAMPLE_PAYLOADS.get(event, {"id": "evt_sample", "event": event, "timestamp": 0})
    return [sample]


# ---------------------------------------------------------------------------
# Outgoing triggers – list valid events
# ---------------------------------------------------------------------------


@router.get("/events", summary="List valid automation event types")
def list_events(user: AuthUser) -> list[str]:
    """Return the list of valid event types that automation hooks can subscribe to."""
    return sorted(VALID_EVENTS)


# ---------------------------------------------------------------------------
# Incoming actions – endpoints that Zapier / Make.com can call
# ---------------------------------------------------------------------------


@router.post("/actions/upload", summary="Upload a document (incoming action)")
def action_upload(
    request: Request,
    db: DbSession,
    user: AuthUser,
    file: UploadFile = File(...),
) -> dict[str, Any]:
    """Accept a document upload from an automation platform.

    This endpoint allows Zapier or Make.com to push a document into
    DocuElevate for processing.  The file is saved to the work directory
    and a background processing task is queued.
    """
    if not file.filename:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required")

    # Sanitise filename to prevent path traversal attacks
    safe_filename = os.path.basename(file.filename)
    if not safe_filename:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required")

    owner_id = user.get("preferred_username") or user.get("email") or user.get("id", "automation")
    workdir = settings.workdir or tempfile.gettempdir()
    upload_dir = os.path.join(workdir, "uploads")
    os.makedirs(upload_dir, exist_ok=True)

    dest_path = os.path.join(upload_dir, safe_filename)
    try:
        contents = file.file.read()
        with open(dest_path, "wb") as f:
            f.write(contents)
    except Exception as exc:
        logger.error("Failed to save uploaded file: %s", exc)
        raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail="Failed to save file")

    # Queue background processing
    task_id = None
    try:
        from app.tasks.process_document import process_document

        result = process_document.delay(dest_path, owner_id)
        task_id = result.id
        logger.info("Automation upload queued: file=%s, task=%s, owner=%s", safe_filename, task_id, owner_id)
    except Exception as exc:
        logger.warning("Could not queue processing task (Celery may be unavailable): %s", exc)

    return {
        "status": "accepted",
        "filename": safe_filename,
        "task_id": task_id,
    }
