import json
from decimal import Decimal
from datetime import datetime, date
from typing import Annotated, Optional

from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel
from sqlalchemy.orm import Session, joinedload

from app.core.database import get_db
from app.core.models import Customer, EmailLog, EmailLogStatus, Invoice, InvoiceItem, InvoiceEvent, InvoiceStatus, Payment
from app.core.security import get_current_user
from app.services.email_service import send_email
from app.services.invoice_service import (
    generate_invoice_number,
    calculate_item,
    resolve_tax_rate,
    recalculate_invoice,
    issue_invoice as _issue_invoice,
    void_invoice as _void_invoice,
    record_payment as _record_payment,
    write_invoice_event,
)
from app.services.invoice_pdf import generate_invoice_pdf, render_invoice_email
from fastapi.responses import Response

router = APIRouter(prefix="/api/invoices", tags=["invoices"])


# ===== Request Models =====

class InvoiceItemCreate(BaseModel):
    product_id: int | None = None
    description: str | None = None
    quantity: float = 1.0
    unit_price: float = 0.0
    tax_rate: float = 0.15
    tax_mode: str = "gst_15"
    custom_tax_rate: float | None = None


class InvoiceCreate(BaseModel):
    customer_id: int
    invoice_date: date
    due_date: date | None = None
    currency: str = "NZD"
    notes: str | None = None
    items: list[InvoiceItemCreate] = []


class InvoiceItemUpdate(BaseModel):
    product_id: int | None = None
    description: str | None = None
    quantity: float | None = None
    unit_price: float | None = None
    tax_rate: float | None = None
    tax_mode: str = "gst_15"
    custom_tax_rate: float | None = None


class InvoiceUpdate(BaseModel):
    due_date: date | None = None
    notes: str | None = None
    items: list[InvoiceItemCreate] | None = None


class PaymentCreate(BaseModel):
    amount: float
    method: str = "bank_transfer"
    reference: str = ""
    notes: str = ""
    received_date: date | None = None


# ===== Response Models =====

class InvoiceItemResponse(BaseModel):
    id: int
    invoice_id: int
    product_id: int | None
    description: str | None
    quantity: float
    unit_price: float
    tax_rate: float
    tax_mode: str
    custom_tax_rate: float | None
    subtotal: float
    tax_amount: float
    total: float

    class Config:
        from_attributes = True


class CustomerSimple(BaseModel):
    id: int
    name: str
    email: str | None

    class Config:
        from_attributes = True


class InvoiceResponse(BaseModel):
    id: int
    invoice_number: str
    customer_id: int
    customer: CustomerSimple | None
    invoice_date: date
    due_date: date | None
    subtotal: float
    total_tax: float
    total_amount: float
    currency: str
    status: str
    notes: str | None
    issued_at: datetime | None
    sent_at: datetime | None
    paid_at: datetime | None
    voided_at: datetime | None
    created_at: datetime | None
    total_paid: float = 0
    balance_due: float = 0
    items: list[InvoiceItemResponse] = []

    class Config:
        from_attributes = True


class InvoiceEventResponse(BaseModel):
    id: int
    invoice_id: int
    event_type: str
    actor_type: str
    actor_name: str | None
    message: str | None
    metadata_json: str | None
    created_at: datetime | None

    class Config:
        from_attributes = True


class PaymentResponse(BaseModel):
    id: int
    invoice_id: int
    paid_at: datetime | None
    received_date: date | None
    amount: float
    currency: str
    method: str
    reference: str | None
    notes: str | None
    created_at: datetime | None

    class Config:
        from_attributes = True


# ===== Helper =====

def _invoice_to_response(invoice: Invoice) -> InvoiceResponse:
    total_paid = sum((Decimal(str(payment.amount)) for payment in getattr(invoice, "payments", []) or []), Decimal("0"))
    total_amount = Decimal(str(invoice.total_amount))
    items = [
        InvoiceItemResponse(
            id=i.id,
            invoice_id=i.invoice_id,
            product_id=i.product_id,
            description=i.description,
            quantity=float(i.quantity),
            unit_price=float(i.unit_price),
            tax_rate=float(i.tax_rate),
            tax_mode=getattr(i, "tax_mode", None) or "gst_15",
            custom_tax_rate=float(i.custom_tax_rate) if getattr(i, "custom_tax_rate", None) is not None else None,
            subtotal=float(i.subtotal),
            tax_amount=float(i.tax_amount),
            total=float(i.total),
        )
        for i in invoice.items
    ]
    return InvoiceResponse(
        id=invoice.id,
        invoice_number=invoice.invoice_number,
        customer_id=invoice.customer_id,
        customer=CustomerSimple.model_validate(invoice.customer) if invoice.customer else None,
        invoice_date=invoice.invoice_date,
        due_date=invoice.due_date,
        subtotal=float(invoice.subtotal),
        total_tax=float(invoice.total_tax),
        total_amount=float(total_amount),
        currency=invoice.currency,
        status=invoice.status.value,
        notes=invoice.notes,
        issued_at=invoice.issued_at,
        sent_at=invoice.sent_at,
        paid_at=invoice.paid_at,
        voided_at=invoice.voided_at,
        created_at=invoice.created_at,
        total_paid=float(total_paid),
        balance_due=float(max(total_amount - total_paid, Decimal("0"))),
        items=items,
    )


def _invoice_email_recipient(invoice: Invoice) -> str | None:
    customer = invoice.customer
    if not customer:
        return None
    if getattr(customer, "email", None):
        return customer.email

    contacts = getattr(customer, "contacts", []) or []
    primary = next((contact for contact in contacts if getattr(contact, "is_primary", False)), None)
    if primary and getattr(primary, "email", None):
        return primary.email
    first_with_email = next((contact for contact in contacts if getattr(contact, "email", None)), None)
    return first_with_email.email if first_with_email else None


def _build_invoice_email(db: Session, invoice: Invoice) -> tuple[str, str]:
    if not invoice.customer:
        raise ValueError("Invoice customer is missing")
    return render_invoice_email(db=db, invoice=invoice, items=invoice.items, customer=invoice.customer)


# ===== Endpoints =====

@router.get("", response_model=list[InvoiceResponse])
def list_invoices(
    current_user: Annotated[dict, Depends(get_current_user)],
    page: int = Query(1, ge=1),
    page_size: int = Query(20, ge=1, le=100),
    customer_id: int | None = None,
    status: str | None = None,
    date_from: date | None = None,
    date_to: date | None = None,
    db: Session = Depends(get_db),
):
    q = db.query(Invoice).options(joinedload(Invoice.customer))
    if customer_id:
        q = q.filter(Invoice.customer_id == customer_id)
    if status:
        q = q.filter(Invoice.status == status)
    if date_from:
        q = q.filter(Invoice.invoice_date >= date_from)
    if date_to:
        q = q.filter(Invoice.invoice_date <= date_to)

    q = q.order_by(Invoice.created_at.desc())
    offset = (page - 1) * page_size
    invoices = q.offset(offset).limit(page_size).all()
    return [_invoice_to_response(i) for i in invoices]


@router.post("", response_model=InvoiceResponse, status_code=201)
def create_invoice(
    data: InvoiceCreate,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    invoice_number = generate_invoice_number(db)

    invoice = Invoice(
        invoice_number=invoice_number,
        customer_id=data.customer_id,
        invoice_date=data.invoice_date,
        due_date=data.due_date,
        currency=data.currency,
        notes=data.notes,
        status=InvoiceStatus.draft,
    )
    db.add(invoice)
    db.flush()  # get invoice.id

    for item_data in data.items:
        resolved_tax_rate = resolve_tax_rate(item_data.tax_mode, item_data.custom_tax_rate)
        stored_custom_tax_rate = resolved_tax_rate if item_data.tax_mode == "custom_rate" else None
        item_row = item_data.model_dump()
        item_row["tax_rate"] = resolved_tax_rate
        item_calc = calculate_item(item_row)
        item = InvoiceItem(
            invoice_id=invoice.id,
            product_id=item_data.product_id,
            description=item_data.description,
            quantity=item_data.quantity,
            unit_price=item_data.unit_price,
            tax_rate=resolved_tax_rate,
            tax_mode=item_data.tax_mode,
            custom_tax_rate=stored_custom_tax_rate,
            subtotal=item_calc["subtotal"],
            tax_amount=item_calc["tax_amount"],
            total=item_calc["total"],
        )
        db.add(item)

    db.flush()
    recalculate_invoice(db, invoice)
    write_invoice_event(db, invoice.id, "created", "user", "system", "Invoice created")
    db.refresh(invoice)

    return _invoice_to_response(invoice)


@router.get("/{invoice_id}", response_model=InvoiceResponse)
def get_invoice(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    invoice = (
        db.query(Invoice)
        .options(joinedload(Invoice.customer), joinedload(Invoice.items), joinedload(Invoice.payments))
        .filter(Invoice.id == invoice_id)
        .first()
    )
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
    return _invoice_to_response(invoice)


@router.put("/{invoice_id}", response_model=InvoiceResponse)
def update_invoice(
    invoice_id: int,
    data: InvoiceUpdate,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")

    if invoice.status != InvoiceStatus.draft:
        raise HTTPException(status_code=400, detail=f"Cannot edit invoice in status: {invoice.status}")

    if data.due_date is not None:
        invoice.due_date = data.due_date
    if data.notes is not None:
        invoice.notes = data.notes

    if data.items is not None:
        # Replace items
        db.query(InvoiceItem).filter(InvoiceItem.invoice_id == invoice_id).delete()
        for item_data in data.items:
            resolved_tax_rate = resolve_tax_rate(item_data.tax_mode, item_data.custom_tax_rate)
            stored_custom_tax_rate = resolved_tax_rate if item_data.tax_mode == "custom_rate" else None
            item_row = item_data.model_dump()
            item_row["tax_rate"] = resolved_tax_rate
            item_calc = calculate_item(item_row)
            item = InvoiceItem(
                invoice_id=invoice.id,
                product_id=item_data.product_id,
                description=item_data.description,
                quantity=item_data.quantity,
                unit_price=item_data.unit_price,
                tax_rate=resolved_tax_rate,
                tax_mode=item_data.tax_mode,
                custom_tax_rate=stored_custom_tax_rate,
                subtotal=item_calc["subtotal"],
                tax_amount=item_calc["tax_amount"],
                total=item_calc["total"],
            )
            db.add(item)
        recalculate_invoice(db, invoice)

    db.commit()
    db.refresh(invoice)
    return _invoice_to_response(invoice)


@router.delete("/{invoice_id}", status_code=204)
def delete_invoice(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
    if invoice.status != InvoiceStatus.draft:
        raise HTTPException(status_code=400, detail=f"Cannot delete invoice in status: {invoice.status}")
    db.query(InvoiceEvent).filter(InvoiceEvent.invoice_id == invoice_id).delete(synchronize_session=False)
    db.query(Payment).filter(Payment.invoice_id == invoice_id).delete(synchronize_session=False)
    db.delete(invoice)
    db.commit()


@router.post("/{invoice_id}/issue", response_model=InvoiceResponse)
def issue_invoice(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    try:
        invoice = _issue_invoice(db, invoice_id)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    db.refresh(invoice)
    return _invoice_to_response(invoice)


@router.post("/{invoice_id}/send", response_model=InvoiceResponse)
async def send_invoice(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    invoice = (
        db.query(Invoice)
        .options(
            joinedload(Invoice.customer).joinedload(Customer.contacts),
            joinedload(Invoice.items),
        )
        .filter(Invoice.id == invoice_id)
        .first()
    )
    if not invoice:
        raise HTTPException(status_code=404, detail="Invoice not found")
    if invoice.status not in (InvoiceStatus.issued, InvoiceStatus.sent, InvoiceStatus.partially_paid):
        raise HTTPException(status_code=400, detail=f"Cannot send invoice in status: {invoice.status}")

    recipient = _invoice_email_recipient(invoice)
    if not recipient:
        raise HTTPException(status_code=400, detail="Customer email is missing")

    try:
        subject, body = _build_invoice_email(db, invoice)
        pdf_bytes = generate_invoice_pdf(db, invoice_id)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    except ImportError as exc:
        raise HTTPException(status_code=503, detail=str(exc)) from exc

    email_result = await send_email(
        recipient,
        subject,
        body,
        attachments=[
            {
                "filename": f"{invoice.invoice_number}.pdf",
                "content": pdf_bytes,
            }
        ],
    )
    db.add(
        EmailLog(
            recipient=recipient,
            subject=subject,
            body=body,
            status=EmailLogStatus.sent if email_result["success"] else EmailLogStatus.failed,
            error_message=email_result["error"],
        )
    )

    if not email_result["success"]:
        db.commit()
        raise HTTPException(status_code=400, detail=email_result["error"] or "SMTP not configured")

    event_type = "sent"
    event_message = "Invoice marked as sent"
    if invoice.status == InvoiceStatus.issued:
        invoice.status = InvoiceStatus.sent
    else:
        event_type = "resent"
        event_message = "Invoice sent again"

    invoice.sent_at = datetime.now()
    db.commit()
    write_invoice_event(db, invoice_id, event_type, "user", "system", event_message)
    db.refresh(invoice)
    return _invoice_to_response(invoice)


@router.post("/{invoice_id}/void", response_model=InvoiceResponse)
def void_invoice(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    try:
        invoice = _void_invoice(db, invoice_id)
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    db.refresh(invoice)
    return _invoice_to_response(invoice)


@router.post("/{invoice_id}/payments", response_model=PaymentResponse, status_code=201)
def add_payment(
    invoice_id: int,
    data: PaymentCreate,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    try:
        payment = _record_payment(
            db,
            invoice_id,
            Decimal(str(data.amount)),
            method=data.method,
            reference=data.reference,
            notes=data.notes,
            received_date=data.received_date,
        )
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))

    return PaymentResponse(
        id=payment.id,
        invoice_id=payment.invoice_id,
        paid_at=payment.paid_at,
        received_date=payment.received_date,
        amount=float(payment.amount),
        currency=payment.currency,
        method=payment.method,
        reference=payment.reference,
        notes=payment.notes,
        created_at=payment.created_at,
    )


@router.get("/{invoice_id}/payments", response_model=list[PaymentResponse])
def get_invoice_payments(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    payments = (
        db.query(Payment)
        .filter(Payment.invoice_id == invoice_id)
        .order_by(Payment.paid_at.asc(), Payment.id.asc())
        .all()
    )
    return [
        PaymentResponse(
            id=payment.id,
            invoice_id=payment.invoice_id,
            paid_at=payment.paid_at,
            received_date=payment.received_date,
            amount=float(payment.amount),
            currency=payment.currency,
            method=payment.method,
            reference=payment.reference,
            notes=payment.notes,
            created_at=payment.created_at,
        )
        for payment in payments
    ]


@router.get("/{invoice_id}/events", response_model=list[InvoiceEventResponse])
def get_invoice_events(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    events = (
        db.query(InvoiceEvent)
        .filter(InvoiceEvent.invoice_id == invoice_id)
        .order_by(InvoiceEvent.created_at.asc())
        .all()
    )
    return [
        InvoiceEventResponse(
            id=e.id,
            invoice_id=e.invoice_id,
            event_type=e.event_type,
            actor_type=e.actor_type,
            actor_name=e.actor_name,
            message=e.message,
            metadata_json=e.metadata_json,
            created_at=e.created_at,
        )
        for e in events
    ]


@router.get("/{invoice_id}/pdf")
def get_invoice_pdf(
    invoice_id: int,
    current_user: Annotated[dict, Depends(get_current_user)],
    db: Session = Depends(get_db),
):
    """Download invoice as PDF."""
    invoice = db.query(Invoice).filter(Invoice.id == invoice_id).first()
    if not invoice:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Invoice not found")

    try:
        pdf_bytes = generate_invoice_pdf(db, invoice_id)
    except ValueError as exc:
        raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc)) from exc
    except ImportError as exc:
        raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE, detail=str(exc)) from exc
    except Exception as exc:
        raise HTTPException(
            status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
            detail=f"Invoice PDF generation failed: {exc}",
        ) from exc

    return Response(
        content=pdf_bytes,
        media_type="application/pdf",
        headers={"Content-Disposition": f'attachment; filename="{invoice.invoice_number}.pdf"'},
    )
