"""
Billing Router
API endpoints for billing operations
"""

from fastapi import APIRouter, HTTPException
from typing import Any, Dict, List
from datetime import datetime, date
import json
from dateutil.relativedelta import relativedelta
from app.core.database import execute_query, get_db_connection, release_db_connection
from psycopg2.extras import RealDictCursor
from app.jobs.reconcile_ordre_drafts import reconcile_ordre_drafts_sync_status
from . import supplier_invoices

router = APIRouter()

# Include supplier invoices router
router.include_router(supplier_invoices.router, prefix="", tags=["Supplier Invoices"])


@router.get("/billing/drafts/sync-dashboard")
async def get_draft_sync_dashboard(limit: int = 20):
    """Operational dashboard data for ordre draft sync lifecycle."""
    try:
        summary = execute_query(
            """
            SELECT
                COUNT(*) FILTER (WHERE sync_status = 'pending') AS pending_count,
                COUNT(*) FILTER (WHERE sync_status = 'exported') AS exported_count,
                COUNT(*) FILTER (WHERE sync_status = 'failed') AS failed_count,
                COUNT(*) FILTER (WHERE sync_status = 'posted') AS posted_count,
                COUNT(*) FILTER (WHERE sync_status = 'paid') AS paid_count,
                COUNT(*) AS total_count
            FROM ordre_drafts
            """,
            (),
        ) or []

        attention = execute_query(
            """
            SELECT
                d.id,
                d.title,
                d.customer_id,
                d.sync_status,
                d.economic_order_number,
                d.economic_invoice_number,
                d.last_sync_at,
                d.updated_at,
                ev.event_type AS latest_event_type,
                ev.created_at AS latest_event_at
            FROM ordre_drafts d
            LEFT JOIN LATERAL (
                SELECT event_type, created_at
                FROM ordre_draft_sync_events
                WHERE draft_id = d.id
                ORDER BY created_at DESC, id DESC
                LIMIT 1
            ) ev ON TRUE
            WHERE d.sync_status IN ('pending', 'failed')
            ORDER BY d.updated_at DESC
            LIMIT %s
            """,
            (max(1, min(limit, 200)),),
        ) or []

        recent_events = execute_query(
            """
            SELECT
                ev.id,
                ev.draft_id,
                ev.event_type,
                ev.from_status,
                ev.to_status,
                ev.event_payload,
                ev.created_by_user_id,
                ev.created_at,
                d.title AS draft_title,
                d.customer_id,
                d.sync_status
            FROM ordre_draft_sync_events ev
            JOIN ordre_drafts d ON d.id = ev.draft_id
            ORDER BY ev.created_at DESC, ev.id DESC
            LIMIT %s
            """,
            (max(1, min(limit, 200)),),
        ) or []

        return {
            "summary": summary[0] if summary else {},
            "attention_items": attention,
            "recent_events": recent_events,
        }
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to load sync dashboard: {e}")


@router.get("/billing/invoices")
async def list_invoices():
    """List all invoices"""
    return {"message": "Billing integration coming soon"}


@router.post("/billing/sync")
async def sync_to_economic():
    """Sync data to e-conomic"""
    return {"message": "e-conomic sync coming soon"}


def _to_date(value: Any) -> date | None:
    if value is None:
        return None
    if isinstance(value, date):
        return value
    if isinstance(value, datetime):
        return value.date()
    text = str(value).strip()
    if not text:
        return None
    try:
        return datetime.fromisoformat(text.replace("Z", "+00:00")).date()
    except ValueError:
        return None


def _next_period(start: date, interval: str) -> date:
    normalized = (interval or "monthly").strip().lower()
    if normalized == "daily":
        return start + relativedelta(days=1)
    if normalized == "biweekly":
        return start + relativedelta(weeks=2)
    if normalized == "quarterly":
        return start + relativedelta(months=3)
    if normalized == "yearly":
        return start + relativedelta(years=1)
    return start + relativedelta(months=1)


@router.post("/billing/subscriptions/preview")
async def preview_subscription_billing(payload: Dict[str, Any]):
    """
    Preview aggregated customer billing from due subscriptions.
    Generates prorata suggestions for approved-but-not-applied price changes.
    """
    try:
        as_of = _to_date(payload.get("as_of")) or date.today()
        customer_id = payload.get("customer_id")

        where = ["s.status = 'active'", "s.next_invoice_date <= %s", "COALESCE(s.billing_blocked, false) = false"]
        params: List[Any] = [as_of]
        if customer_id:
            where.append("s.customer_id = %s")
            params.append(customer_id)

        subscriptions = execute_query(
            f"""
            SELECT
                s.id,
                s.customer_id,
                c.name AS customer_name,
                s.product_name,
                s.billing_interval,
                s.billing_direction,
                s.invoice_merge_key,
                s.next_invoice_date,
                s.period_start,
                s.price,
                COALESCE(
                    (
                        SELECT json_agg(
                            json_build_object(
                                'id', i.id,
                                'description', i.description,
                                'quantity', i.quantity,
                                'unit_price', i.unit_price,
                                'line_total', i.line_total,
                                'asset_id', i.asset_id,
                                'period_from', i.period_from,
                                'period_to', i.period_to,
                                'billing_blocked', i.billing_blocked
                            ) ORDER BY i.line_no ASC, i.id ASC
                        )
                        FROM sag_subscription_items i
                        WHERE i.subscription_id = s.id
                    ),
                    '[]'::json
                ) AS line_items
            FROM sag_subscriptions s
            LEFT JOIN customers c ON c.id = s.customer_id
            WHERE {' AND '.join(where)}
            ORDER BY s.customer_id, s.next_invoice_date, s.id
            """,
            tuple(params),
        ) or []

        groups: Dict[str, Dict[str, Any]] = {}
        for sub in subscriptions:
            merge_key = sub.get("invoice_merge_key") or f"cust-{sub['customer_id']}"
            key = f"{sub['customer_id']}|{merge_key}|{sub.get('billing_direction') or 'forward'}|{sub.get('next_invoice_date')}"
            grp = groups.setdefault(
                key,
                {
                    "customer_id": sub["customer_id"],
                    "customer_name": sub.get("customer_name"),
                    "merge_key": merge_key,
                    "billing_direction": sub.get("billing_direction") or "forward",
                    "invoice_date": str(sub.get("next_invoice_date")),
                    "coverage_start": None,
                    "coverage_end": None,
                    "subscription_ids": [],
                    "line_count": 0,
                    "amount_total": 0.0,
                },
            )

            sub_id = int(sub["id"])
            grp["subscription_ids"].append(sub_id)
            start = _to_date(sub.get("period_start") or sub.get("next_invoice_date")) or as_of
            end = _next_period(start, sub.get("billing_interval") or "monthly")
            grp["coverage_start"] = str(start) if grp["coverage_start"] is None or str(start) < grp["coverage_start"] else grp["coverage_start"]
            grp["coverage_end"] = str(end) if grp["coverage_end"] is None or str(end) > grp["coverage_end"] else grp["coverage_end"]

            for item in sub.get("line_items") or []:
                if item.get("billing_blocked"):
                    continue
                grp["line_count"] += 1
                grp["amount_total"] += float(item.get("line_total") or 0)

        price_changes = execute_query(
            """
            SELECT
                spc.id,
                spc.subscription_id,
                spc.subscription_item_id,
                spc.old_unit_price,
                spc.new_unit_price,
                spc.effective_date,
                spc.approval_status,
                spc.reason,
                s.period_start,
                s.billing_interval
            FROM subscription_price_changes spc
            JOIN sag_subscriptions s ON s.id = spc.subscription_id
            WHERE spc.deleted_at IS NULL
              AND spc.approval_status IN ('approved', 'pending')
              AND spc.effective_date <= %s
            ORDER BY spc.effective_date ASC, spc.id ASC
            """,
            (as_of,),
        ) or []

        prorata_suggestions: List[Dict[str, Any]] = []
        for change in price_changes:
            period_start = _to_date(change.get("period_start"))
            if not period_start:
                continue
            period_end = _next_period(period_start, change.get("billing_interval") or "monthly")
            eff = _to_date(change.get("effective_date"))
            if not eff:
                continue
            if eff <= period_start or eff >= period_end:
                continue

            total_days = max((period_end - period_start).days, 1)
            remaining_days = max((period_end - eff).days, 0)
            old_price = float(change.get("old_unit_price") or 0)
            new_price = float(change.get("new_unit_price") or 0)
            delta = new_price - old_price
            prorata_amount = round(delta * (remaining_days / total_days), 2)
            if prorata_amount == 0:
                continue

            prorata_suggestions.append(
                {
                    "price_change_id": change.get("id"),
                    "subscription_id": change.get("subscription_id"),
                    "subscription_item_id": change.get("subscription_item_id"),
                    "effective_date": str(eff),
                    "period_start": str(period_start),
                    "period_end": str(period_end),
                    "old_unit_price": old_price,
                    "new_unit_price": new_price,
                    "remaining_days": remaining_days,
                    "total_days": total_days,
                    "suggested_adjustment": prorata_amount,
                    "adjustment_type": "debit" if prorata_amount > 0 else "credit",
                    "reason": change.get("reason"),
                    "requires_manual_approval": True,
                }
            )

        return {
            "status": "preview",
            "as_of": str(as_of),
            "group_count": len(groups),
            "groups": list(groups.values()),
            "prorata_suggestions": prorata_suggestions,
        }
    except HTTPException:
        raise
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to preview subscription billing: {e}")


@router.post("/billing/prorata-adjustments/draft")
async def create_prorata_adjustment_draft(payload: Dict[str, Any]):
    """
    Create a manual adjustment draft from an approved prorata suggestion.
    Payload expects customer_id, subscription_id, amount, reason and optional effective dates.
    """
    conn = get_db_connection()
    try:
        customer_id = payload.get("customer_id")
        subscription_id = payload.get("subscription_id")
        amount = float(payload.get("amount") or 0)
        reason = (payload.get("reason") or "Prorata justering").strip()
        effective_date = _to_date(payload.get("effective_date")) or date.today()
        period_start = _to_date(payload.get("period_start"))
        period_end = _to_date(payload.get("period_end"))

        if not customer_id:
            raise HTTPException(status_code=400, detail="customer_id is required")
        if not subscription_id:
            raise HTTPException(status_code=400, detail="subscription_id is required")
        if amount == 0:
            raise HTTPException(status_code=400, detail="amount must be non-zero")

        with conn.cursor(cursor_factory=RealDictCursor) as cursor:
            cursor.execute(
                """
                SELECT id, customer_id, product_name
                FROM sag_subscriptions
                WHERE id = %s
                """,
                (subscription_id,),
            )
            sub = cursor.fetchone()
            if not sub:
                raise HTTPException(status_code=404, detail="Subscription not found")
            if int(sub.get("customer_id") or 0) != int(customer_id):
                raise HTTPException(status_code=400, detail="customer_id mismatch for subscription")

            adjustment_label = "Prorata tillæg" if amount > 0 else "Prorata kredit"
            line = {
                "product": {
                    "productNumber": "PRORATA",
                    "description": f"{adjustment_label}: {sub.get('product_name') or 'Abonnement'}"
                },
                "quantity": 1,
                "unitNetPrice": amount,
                "totalNetAmount": amount,
                "discountPercentage": 0,
                "metadata": {
                    "subscription_id": subscription_id,
                    "effective_date": str(effective_date),
                    "period_start": str(period_start) if period_start else None,
                    "period_end": str(period_end) if period_end else None,
                    "reason": reason,
                    "manual_approval": True,
                }
            }

            cursor.execute(
                """
                INSERT INTO ordre_drafts (
                    title,
                    customer_id,
                    lines_json,
                    notes,
                    coverage_start,
                    coverage_end,
                    billing_direction,
                    source_subscription_ids,
                    invoice_aggregate_key,
                    layout_number,
                    created_by_user_id,
                    sync_status,
                    export_status_json,
                    updated_at
                ) VALUES (
                    %s, %s, %s::jsonb, %s,
                    %s, %s, %s, %s, %s,
                    %s, %s, %s, %s::jsonb, CURRENT_TIMESTAMP
                )
                RETURNING id, created_at
                """,
                (
                    f"Manuel {adjustment_label}",
                    customer_id,
                    json.dumps([line], ensure_ascii=False),
                    reason,
                    period_start,
                    period_end,
                    "backward",
                    [subscription_id],
                    f"manual-prorata-{customer_id}",
                    1,
                    payload.get("created_by_user_id"),
                    "pending",
                    json.dumps(
                        {
                            "source": "prorata_manual",
                            "subscription_id": subscription_id,
                            "effective_date": str(effective_date),
                        },
                        ensure_ascii=False,
                    ),
                ),
            )
            created = cursor.fetchone()

        conn.commit()
        return {
            "status": "draft_created",
            "draft_id": created.get("id") if created else None,
            "created_at": created.get("created_at") if created else None,
            "subscription_id": subscription_id,
            "amount": amount,
        }
    except HTTPException:
        conn.rollback()
        raise
    except Exception as e:
        conn.rollback()
        raise HTTPException(status_code=500, detail=f"Failed to create prorata adjustment draft: {e}")
    finally:
        release_db_connection(conn)


@router.post("/billing/drafts/reconcile-sync-status")
async def reconcile_draft_sync_status(payload: Dict[str, Any]):
    """
    Reconcile ordre_drafts sync_status from known economic references.
    Rules:
    - pending/failed + economic_order_number -> exported
    - exported + economic_invoice_number -> posted
    - posted + mark_paid_ids contains draft id -> paid
    """
    try:
        apply_changes = bool(payload.get("apply", False))
        result = await reconcile_ordre_drafts_sync_status(apply_changes=apply_changes)

        mark_paid_ids = set(int(x) for x in (payload.get("mark_paid_ids") or []) if str(x).isdigit())
        if apply_changes and mark_paid_ids:
            conn = get_db_connection()
            try:
                with conn.cursor(cursor_factory=RealDictCursor) as cursor:
                    for draft_id in mark_paid_ids:
                        cursor.execute("SELECT sync_status FROM ordre_drafts WHERE id = %s", (draft_id,))
                        before = cursor.fetchone()
                        from_status = (before or {}).get("sync_status")
                        cursor.execute(
                            """
                            UPDATE ordre_drafts
                            SET sync_status = 'paid',
                                last_sync_at = CURRENT_TIMESTAMP,
                                updated_at = CURRENT_TIMESTAMP,
                                last_exported_at = CURRENT_TIMESTAMP
                            WHERE id = %s
                              AND sync_status = 'posted'
                            RETURNING id
                            """,
                            (draft_id,),
                        )
                        updated = cursor.fetchone()
                        if updated:
                            cursor.execute(
                                """
                                INSERT INTO ordre_draft_sync_events (
                                    draft_id,
                                    event_type,
                                    from_status,
                                    to_status,
                                    event_payload,
                                    created_by_user_id
                                ) VALUES (%s, %s, %s, %s, %s::jsonb, NULL)
                                """,
                                (
                                    draft_id,
                                    'sync_status_manual_paid',
                                    from_status,
                                    'paid',
                                    '{"source":"billing_reconcile_endpoint"}',
                                ),
                            )
                conn.commit()
            finally:
                release_db_connection(conn)

        if mark_paid_ids:
            result["mark_paid_ids"] = sorted(mark_paid_ids)
        return result
    except Exception as e:
        raise HTTPException(status_code=500, detail=f"Failed to reconcile draft sync status: {e}")
