"""
ABOS AI Worker – API Router (Unified Finance Schema)
=====================================================
Defines all endpoints for processing statements, aligning with the Spring Boot
Unified Finance star schema.
"""

from __future__ import annotations

import logging
from typing import Any

from fastapi import APIRouter, BackgroundTasks, HTTPException, UploadFile, status

from app.config import settings
from app.llm_client import call_openrouter_layered
from app.pdf_utils import extract_text_from_pdf
from app.schemas import (
    DataPointsResponse,
    DocumentRecord,
    ExtractRequest,
    ExtractResponse,
    EntityType,
    JobStatus,
    StatementResult,
    HumanInLoopRequest,
    HumanInLoopResponse,
    UploadResponse,
)
from app.store import document_store
from app.webhook import deliver_webhook

logger = logging.getLogger(__name__)

router = APIRouter()


# ============================================================================
# A-1 │ POST /upload
# ============================================================================

@router.post(
    "/upload",
    response_model=UploadResponse,
    status_code=status.HTTP_201_CREATED,
    summary="Upload a PDF for processing",
    tags=["Backend-Facing"],
)
async def upload_pdf(file: UploadFile) -> UploadResponse:
    """Ingest a PDF and store it transiently; return the generated document_id."""
    content_type = file.content_type or ""
    if content_type not in ("application/pdf", "application/octet-stream"):
        raise HTTPException(
            status_code=status.HTTP_415_UNSUPPORTED_MEDIA_TYPE,
            detail=f"Unsupported file type '{content_type}'. Only PDF files are accepted.",
        )

    pdf_bytes = await file.read()
    if not pdf_bytes:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Uploaded file is empty.",
        )

    try:
        raw_text, page_count = extract_text_from_pdf(pdf_bytes)
    except (ValueError, RuntimeError) as exc:
        logger.error("PDF extraction failed: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Could not extract text from PDF: {exc}",
        ) from exc

    document_id = document_store.generate_id()
    record = DocumentRecord(
        document_id=document_id,
        filename=file.filename or "unknown.pdf",
        page_count=page_count,
        raw_text=raw_text,
        status=JobStatus.PENDING,
    )
    await document_store.save(record)

    logger.info(
        "Document uploaded: id=%s filename=%s pages=%d",
        document_id,
        record.filename,
        page_count,
    )

    return UploadResponse(
        document_id=document_id,
        filename=record.filename,
        page_count=page_count,
        status=JobStatus.PENDING,
    )


# ============================================================================
# A-2 │ POST /extract
# ============================================================================

@router.post(
    "/extract",
    response_model=ExtractResponse,
    status_code=status.HTTP_200_OK,
    summary="Extract structured data from an uploaded document via LLM",
    tags=["Backend-Facing"],
)
async def extract_data(
    body: ExtractRequest,
    background_tasks: BackgroundTasks,
) -> ExtractResponse:
    """Call the LLM and store extracted data_points for the given document."""
    record = await document_store.get(body.document_id)
    if record is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Document '{body.document_id}' not found or has expired.",
        )

    record.status = JobStatus.EXTRACTING
    record.entity_type = body.entity_type
    record.entity_id = body.entity_id
    record.tenant_id = body.tenant_id
    record.user_id = body.user_id
    await document_store.update(record)

    # ── Branching prompt configuration based on EntityType ──────────────────
    if body.entity_type == EntityType.BANK:
        prompt = (
            "Extract the following bank statement details. "
            "Respond ONLY with a valid JSON object matching this structure:\n"
            "{\n"
            "  \"accountHolder\": \"Full Name\",\n"
            "  \"accountNumber\": \"Account Number\",\n"
            "  \"statementPeriod\": \"YYYY-MM\",\n"
            "  \"openingBalance\": 123.45,\n"
            "  \"closingBalance\": 678.90,\n"
            "  \"currency\": \"USD\",\n"
            "  \"transactions\": [\n"
            "    {\n"
            "      \"date\": \"YYYY-MM-DD\",\n"
            "      \"description\": \"Transaction Narrative\",\n"
            "      \"debit\": 10.00,\n"
            "      \"credit\": null,\n"
            "      \"balance\": 113.45\n"
            "    }\n"
            "  ]\n"
            "}\n"
            "Note: Set associatedCardNumber and homeTaxPercent to null for all transactions, "
            "as this is a Bank statement."
        )
    else:
        # CreditCard extraction
        prompt = (
            "Extract the following credit card statement details. "
            "Respond ONLY with a valid JSON object matching this structure:\n"
            "{\n"
            "  \"accountHolder\": \"Full Name\",\n"
            "  \"accountNumber\": \"Card Number\",\n"
            "  \"statementPeriod\": \"YYYY-MM\",\n"
            "  \"openingBalance\": 123.45,\n"
            "  \"closingBalance\": 678.90,\n"
            "  \"currency\": \"USD\",\n"
            "  \"transactions\": [\n"
            "    {\n"
            "      \"date\": \"YYYY-MM-DD\",\n"
            "      \"description\": \"Transaction Narrative\",\n"
            "      \"debit\": 10.00,\n"
            "      \"credit\": null,\n"
            "      \"balance\": 113.45,\n"
            "      \"associatedCardNumber\": \"4321\",\n"
            "      \"homeTaxPercent\": 15.00\n"
            "    }\n"
            "  ]\n"
            "}\n"
            "Ensure you extract associatedCardNumber and homeTaxPercent (0-100) if visible."
        )

    # ── Call LLM (multi-layer: primary model, then fallbacks for any gaps) ──
    try:
        data_points, model_used, extraction_layers = await call_openrouter_layered(
            openrouter_key=body.openrouter_key,
            prompt=prompt,
            document_text=record.raw_text,
            model=body.model,
            fallback_models=settings.openrouter_fallback_models,
        )
    except Exception as exc:
        record.status = JobStatus.FAILED
        record.error_detail = f"LLM Call failed: {exc}"
        await document_store.update(record)
        logger.error("LLM extraction failed: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_502_BAD_GATEWAY,
            detail=f"LLM extraction failed: {exc}",
        ) from exc

    # Inject metadata into the result
    data_points["entityType"] = body.entity_type.value
    data_points["entityId"] = body.entity_id

    # ── Parse and validate into Pydantic models ───────────────────────────
    try:
        statement_result = StatementResult.model_validate(data_points)
    except Exception as exc:
        record.status = JobStatus.FAILED
        record.error_detail = f"Pydantic Validation failed on LLM output: {exc}"
        await document_store.update(record)
        logger.error("Extracted data validation failed: %s", exc)
        raise HTTPException(
            status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
            detail=f"Extracted data did not match the expected schema: {exc}",
        ) from exc

    # Apply conditional schema modifications:
    if body.entity_type == EntityType.BANK:
        for tx in statement_result.transactions:
            tx.associated_card_number = None
            tx.home_tax_percent = None

    record.statement_result = statement_result
    record.model_used = model_used
    record.extraction_layers = extraction_layers

    # ── Pre-webhook validation checks ─────────────────────────────────────
    if not statement_result.transactions:
        # If transactions list is empty, transition to NEEDS_REVIEW
        record.status = JobStatus.NEEDS_REVIEW
        record.error_detail = "Statement contains no transactions. Requires manual human verification."
        await document_store.update(record)
        logger.warning("Empty transactions for document=%s; transitioned to NEEDS_REVIEW.", body.document_id)
        return ExtractResponse(
            documentId=body.document_id,
            status=JobStatus.NEEDS_REVIEW,
            modelUsed=model_used,
            entityType=body.entity_type,
            statementResult=statement_result,
        )

    # ── HITL branch: return extracted data for human review, do NOT auto-save ──
    if not body.auto_post:
        record.status = JobStatus.NEEDS_REVIEW
        await document_store.update(record)
        logger.info(
            "Extraction complete for document=%s; awaiting human review (auto_post=false).",
            body.document_id,
        )
        return ExtractResponse(
            documentId=body.document_id,
            status=JobStatus.NEEDS_REVIEW,
            modelUsed=model_used,
            entityType=body.entity_type,
            statementResult=statement_result,
        )

    # Transition to POSTING (since validation passes)
    record.status = JobStatus.POSTING
    await document_store.update(record)

    # ── Trigger asynchronous webhook callback (auto-save path) ─────────────
    webhook_url = body.webhook_url or "http://localhost:8080/api/finance/ai-processing/event"
    background_tasks.add_task(
        deliver_webhook,
        document_id=body.document_id,
        verified_data=statement_result,
        spring_boot_webhook_url=webhook_url,
        tenant_id=body.tenant_id,
        user_id=body.user_id,
        entity_type=body.entity_type,
        entity_id=body.entity_id,
    )

    return ExtractResponse(
        documentId=body.document_id,
        status=JobStatus.POSTING,
        modelUsed=model_used,
        extractionLayers=extraction_layers,
        entityType=body.entity_type,
        statementResult=statement_result,
    )


# ============================================================================
# B-3 │ GET /data-points/{document_id}
# ============================================================================

@router.get(
    "/data-points/{document_id}",
    response_model=DataPointsResponse,
    status_code=status.HTTP_200_OK,
    summary="Retrieve extracted data points for human review",
    tags=["Frontend-Facing"],
)
async def get_data_points(document_id: str) -> DataPointsResponse:
    """Return stored data_points for HITL review on the React frontend."""
    record = await document_store.get(document_id)
    if record is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Document '{document_id}' not found or has expired.",
        )

    # Must have completed extraction step
    if record.status in (JobStatus.PENDING, JobStatus.EXTRACTING) or record.statement_result is None:
        raise HTTPException(
            status_code=status.HTTP_409_CONFLICT,
            detail=f"Document '{document_id}' has status '{record.status.value}'. Process statement first.",
        )

    return DataPointsResponse(
        documentId=document_id,
        status=record.status,
        entityType=record.entity_type or EntityType.BANK,
        statementResult=record.statement_result,
        modelUsed=record.model_used,
        extractionLayers=record.extraction_layers,
    )


# ============================================================================
# B-4 │ POST /human-in-loop
# ============================================================================

@router.post(
    "/human-in-loop",
    response_model=HumanInLoopResponse,
    status_code=status.HTTP_202_ACCEPTED,
    summary="Submit human-verified data and trigger Spring Boot webhook",
    tags=["Frontend-Facing"],
)
async def human_in_loop(
    body: HumanInLoopRequest,
    background_tasks: BackgroundTasks,
) -> HumanInLoopResponse:
    """Accept verified data from the HITL UI and queue webhook dispatch."""
    record = await document_store.get(body.document_id)
    if record is None:
        raise HTTPException(
            status_code=status.HTTP_404_NOT_FOUND,
            detail=f"Document '{body.document_id}' not found or has expired.",
        )

    # Force manual validation on verified data payload
    if body.verified_data.entity_type == EntityType.BANK:
        for tx in body.verified_data.transactions:
            tx.associated_card_number = None
            tx.home_tax_percent = None

    record.status = JobStatus.POSTING
    record.statement_result = body.verified_data
    await document_store.update(record)

    background_tasks.add_task(
        deliver_webhook,
        document_id=body.document_id,
        verified_data=body.verified_data,
        spring_boot_webhook_url=body.spring_boot_webhook_url,
        tenant_id=body.tenant_id,
        user_id=body.user_id,
        entity_type=body.verified_data.entity_type,
        entity_id=body.verified_data.entity_id,
    )

    return HumanInLoopResponse(
        message="Verification accepted. Webhook delivery is in progress.",
        documentId=body.document_id,
    )


# ============================================================================
# Meta Endpoints
# ============================================================================

@router.get(
    "/health",
    summary="Health check",
    description="Returns 200 OK when the service is running.",
    tags=["Meta"],
)
async def health_check():
    return {"status": "ok", "service": "ABOS AI Worker"}


@router.get(
    "/info",
    summary="Service information",
    description="Returns version and configuration details.",
    tags=["Meta"],
)
async def info():
    return {
        "service": settings.app_name,
        "version": settings.app_version,
        "openrouter_default_model": settings.openrouter_default_model,
        "openrouter_fallback_models": settings.openrouter_fallback_models,
        "document_ttl_seconds": settings.document_ttl_seconds,
        "cors_origins": settings.cors_origins,
    }
