import json
from datetime import date, datetime
from typing import Annotated, Any, Optional

from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel, Field
from sqlalchemy.orm import Session

from app.api.config import get_default_currency
from app.api.customers import _customer_to_response, _normalize_customer_data, _sync_contacts
from app.core.database import get_db
from app.core.models import AIUsageSource, Customer, Invoice, InvoiceEvent, InvoiceItem, InvoiceStatus, Product, ProductStatus, User
from app.core.security import has_permission, require_permission
from app.services.ai_quota_service import AIQuotaExceeded, complete_ai_credit, fail_ai_credit, reserve_ai_credit
from app.services.ai_service import (
    chat_with_ai,
    parse_ai_draft,
    validate_customer_data,
    validate_draft_data,
)
from app.services.ai_query_executor import build_query_from_message, execute_ai_query
from app.services.invoice_service import calculate_item, generate_invoice_number, recalculate_invoice

router = APIRouter(prefix="/api/ai", tags=["ai"])


class ChatRequest(BaseModel):
    message: str
    thread_id: Optional[str] = None
    context: list[dict] = Field(default_factory=list)


class ChatResponse(BaseModel):
    reply: str
    action: Optional[str] = None
    draft_id: Optional[int] = None
    customer_id: Optional[int] = None
    source: Optional[str] = None
    tool_name: Optional[str] = None
    result: Optional[dict[str, Any]] = None


def required_permission_for_ai_action(action: str, query_type: str | None = None) -> str:
    if action == "create_invoice_draft": return "invoices.write"
    if action == "create_customer": return "customers.write"
    return {"revenue": "finance.read", "stats": "finance.read", "invoice": "invoices.read", "invoices": "invoices.read", "customer": "customers.read", "customers": "customers.read", "product": "products.read", "products": "products.read", "expense": "expenses.read", "expenses": "expenses.read", "subscription": "subscriptions.read", "subscriptions": "subscriptions.read", "reminder": "reminders.read", "reminders": "reminders.read", "gst": "gst.read", "gst_summary": "gst.read"}.get(query_type or "", "ai_chat.read")


def _ensure_ai_action_allowed(user: User | None, action: str, query_type: str | None = None) -> None:
    if user is not None and not has_permission(user, required_permission_for_ai_action(action, query_type)):
        raise HTTPException(status_code=403, detail="You do not have permission for this AI action")


def _prepare_customer_payload(customer_data: dict) -> dict:
    prepared = dict(customer_data or {})
    contacts = prepared.get("contacts", [])
    if contacts is None or not isinstance(contacts, list):
        contacts = []

    fallback_name = str(prepared.get("contact_name") or "").strip()
    fallback_email = str(prepared.get("email") or "").strip()
    fallback_phone = str(prepared.get("phone") or "").strip()
    fallback_mobile = str(prepared.get("mobile") or "").strip()

    normalized_contacts = []
    for contact in contacts:
        if not isinstance(contact, dict):
            continue

        entry = dict(contact)
        name = str(entry.get("name") or "").strip()
        email = str(entry.get("email") or "").strip()
        phone = str(entry.get("phone") or "").strip()
        mobile = str(entry.get("mobile") or "").strip()
        title = str(entry.get("title") or "").strip()
        notes = str(entry.get("notes") or "").strip()

        if not any([name, email, phone, mobile, title, notes]):
            continue

        if not name:
            entry["name"] = fallback_name or str(prepared.get("company_name") or prepared.get("name") or "").strip()
        if not entry.get("email") and fallback_email:
            entry["email"] = fallback_email
        if not entry.get("phone") and fallback_phone:
            entry["phone"] = fallback_phone
        if not entry.get("mobile") and fallback_mobile:
            entry["mobile"] = fallback_mobile
        normalized_contacts.append(entry)

    if not normalized_contacts and (fallback_name or fallback_email or fallback_phone or fallback_mobile):
        normalized_contacts.append(
            {
                "name": fallback_name or str(prepared.get("company_name") or prepared.get("name") or "").strip(),
                "email": fallback_email or None,
                "phone": fallback_phone or None,
                "mobile": fallback_mobile or None,
                "is_primary": True,
            }
        )

    prepared["contacts"] = normalized_contacts
    return prepared


def _parse_draft_date(value) -> "date | None":
    """Coerce an AI-supplied date to a date, or None if empty/invalid.

    The AI may omit the date, return an empty string, or hallucinate a
    malformed value; none of those should become a stored invoice date.
    """
    if isinstance(value, date):
        return value
    if not value or not isinstance(value, str):
        return None
    try:
        return date.fromisoformat(value.strip())
    except ValueError:
        return None


def _create_invoice_draft(db: Session, draft_data: dict, tenant_id: int) -> tuple[str, str, int | None]:
    valid, error = validate_draft_data(draft_data)
    if not valid:
        return f"AI draft validation error: {error}", "error", None

    customer_name = draft_data["customer_name"]
    customer = db.query(Customer).filter(Customer.tenant_id == tenant_id, Customer.name.ilike(f"%{customer_name}%")).first()
    if not customer:
        return f"Customer '{customer_name}' not found. Please check the name.", "query", None

    resolved_items = []
    for index, item_data in enumerate(draft_data["items"], start=1):
        product_id = item_data.get("product_id")
        if product_id in (None, ""):
            resolved_items.append((item_data, None))
            continue
        try:
            product_id = int(product_id)
        except (TypeError, ValueError):
            return f"Item {index}: invalid product ID.", "error", None
        product = (
            db.query(Product)
            .filter(
                Product.id == product_id,
                Product.tenant_id == tenant_id,
                Product.status == ProductStatus.active,
            )
            .first()
        )
        if not product:
            return f"Item {index}: product is unavailable.", "error", None
        resolved_items.append((item_data, product))

    invoice = Invoice(
        tenant_id=tenant_id,
        invoice_number=generate_invoice_number(db, tenant_id),
        customer_id=customer.id,
        invoice_date=_parse_draft_date(draft_data.get("invoice_date")) or datetime.now().date(),
        due_date=_parse_draft_date(draft_data.get("due_date")),
        currency=draft_data.get("currency") or get_default_currency(db, tenant_id),
        notes=draft_data.get("notes", ""),
        status=InvoiceStatus.draft,
    )
    db.add(invoice)
    db.flush()

    for item_data, product in resolved_items:
        qty = float(item_data.get("quantity", 1))
        price = float(product.unit_price) if product else float(item_data.get("unit_price", 0))
        rate = float(product.tax_rate) if product else float(item_data.get("tax_rate", 0.15))
        calculated = calculate_item({"quantity": qty, "unit_price": price, "tax_rate": rate})

        item = InvoiceItem(
            tenant_id=tenant_id,
            invoice_id=invoice.id,
            product_id=product.id if product else None,
            description=product.name if product else item_data.get("description", "Item"),
            quantity=qty,
            unit_price=price,
            tax_rate=rate,
            subtotal=calculated["subtotal"],
            tax_amount=calculated["tax_amount"],
            total=calculated["total"],
        )
        db.add(item)

    # autoflush is off, so flush the new items before recalc queries them.
    db.flush()
    recalculate_invoice(db, invoice)

    event = InvoiceEvent(
        tenant_id=tenant_id,
        invoice_id=invoice.id,
        event_type="ai_drafted",
        actor_type="ai",
        actor_name="AI Assistant",
        message="AI draft created from chat",
        metadata_json=json.dumps({"raw": draft_data}),
    )
    db.add(event)
    db.commit()

    reply = (
        f"Invoice draft created: {invoice.invoice_number}\n\n"
        "Review it in the Invoices page and click 'Issue' when ready."
    )
    return reply, "create_draft", invoice.id


def _create_customer(db: Session, customer_data: dict, tenant_id: int) -> tuple[str, str, int | None]:
    prepared_data = _prepare_customer_payload(customer_data)
    valid, error = validate_customer_data(prepared_data)
    if not valid:
        return f"AI customer validation error: {error}", "error", None

    customer_name = str(prepared_data.get("name") or prepared_data.get("company_name") or "").strip()
    existing = (
        db.query(Customer)
        .filter(
            Customer.tenant_id == tenant_id,
            (Customer.name.ilike(customer_name)) | (Customer.company_name.ilike(customer_name)),
        )
        .first()
    )
    if existing:
        return f"Customer '{customer_name}' already exists.", "query", None

    payload = {
        "name": prepared_data.get("name"),
        "company_name": prepared_data.get("company_name"),
        "contact_name": prepared_data.get("contact_name"),
        "email": prepared_data.get("email"),
        "phone": prepared_data.get("phone"),
        "mobile": prepared_data.get("mobile"),
        "address": prepared_data.get("address"),
        "website": prepared_data.get("website"),
        "customer_type": prepared_data.get("customer_type", "project"),
        "status": prepared_data.get("status", "active"),
        "notes": prepared_data.get("notes"),
        "contacts": prepared_data.get("contacts", []),
    }

    normalized_data, contacts = _normalize_customer_data(payload)
    customer = Customer(**normalized_data)
    customer.tenant_id = tenant_id
    if contacts:
        _sync_contacts(db, customer, contacts)
        for contact in customer.contacts:
            contact.tenant_id = tenant_id

    db.add(customer)
    db.commit()
    db.refresh(customer)

    customer_response = _customer_to_response(db, customer)
    reply = (
        f"Customer created: {customer_response.name}\n\n"
        f"Company: {customer_response.company_name or '-'}\n"
        f"Primary contact: {customer_response.contact_name or '-'}\n"
        f"Email: {customer_response.email or '-'}\n"
        f"Phone: {customer_response.phone or '-'}\n"
        f"Mobile: {customer_response.mobile or '-'}"
    )
    return reply, "create_customer", customer.id


async def _handle_ai_chat_message(
    db: Session,
    message: str,
    context: Optional[list[dict]] = None,
    *,
    tenant_id: int,
    user: User | None = None,
) -> ChatResponse:
    direct_query = build_query_from_message(message)
    if direct_query:
        _ensure_ai_action_allowed(user, "query", direct_query.get("query_type"))
        query_result = execute_ai_query(db, direct_query, tenant_id=tenant_id)
        return ChatResponse(
            reply=query_result.get("reply", ""),
            action=query_result.get("action"),
            source=query_result.get("source"),
            tool_name=query_result.get("tool_name"),
            result=query_result.get("result"),
        )

    response = await chat_with_ai(message, context=context, db=db, tenant_id=tenant_id)
    reply = response.get("content", "Sorry, AI is currently unavailable.")

    draft_data = parse_ai_draft(reply)
    action = None
    draft_id = None
    customer_id = None
    source = None
    tool_name = None
    result = None

    if draft_data and draft_data.get("action") == "query":
        _ensure_ai_action_allowed(user, "query", draft_data.get("query_type"))
        query_result = execute_ai_query(db, draft_data, tenant_id=tenant_id)
        reply = query_result.get("reply", reply)
        action = query_result.get("action")
        source = query_result.get("source")
        tool_name = query_result.get("tool_name")
        result = query_result.get("result")
    elif draft_data and draft_data.get("action") == "create_invoice_draft":
        _ensure_ai_action_allowed(user, "create_invoice_draft")
        reply, action, draft_id = _create_invoice_draft(db, draft_data, tenant_id)
    elif draft_data and draft_data.get("action") == "create_customer":
        _ensure_ai_action_allowed(user, "create_customer")
        prepared_data = _prepare_customer_payload(draft_data)
        valid, error = validate_customer_data(prepared_data)
        if not valid:
            reply = f"AI customer validation error: {error}"
            action = "error"
        elif not prepared_data.get("confirmed"):
            action = "confirm_create_customer"
        else:
            reply, action, customer_id = _create_customer(db, prepared_data, tenant_id)

    return ChatResponse(
        reply=reply,
        action=action,
        draft_id=draft_id,
        customer_id=customer_id,
        source=source,
        tool_name=tool_name,
        result=result,
    )


async def handle_ai_chat_message(
    db: Session,
    message: str,
    context: Optional[list[dict]] = None,
    *,
    tenant_id: int,
    source: AIUsageSource = AIUsageSource.web,
    user_id: int | None = None,
    user: User | None = None,
) -> ChatResponse:
    usage = reserve_ai_credit(db, tenant_id=tenant_id, source=source, user_id=user_id)
    try:
        response = await _handle_ai_chat_message(db=db, message=message, context=context, tenant_id=tenant_id, user=user)
    except Exception as exc:
        fail_ai_credit(db, usage, str(exc))
        raise
    complete_ai_credit(db, usage)
    return response


@router.post("/chat", response_model=ChatResponse)
async def chat(
    req: ChatRequest,
    current_user: Annotated[dict, Depends(require_permission("ai_chat.read"))],
    db: Session = Depends(get_db),
):
    """Chat with the AI assistant."""
    try:
        return await handle_ai_chat_message(
            db=db,
            message=req.message,
            context=req.context,
            tenant_id=current_user.tenant_id,
            user_id=current_user.id,
            user=current_user,
        )
    except AIQuotaExceeded as exc:
        raise HTTPException(status_code=429, detail=f"AI {exc.window} credit limit reached") from exc
