import json
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import JSONResponse
from sqlalchemy.orm import Session

from database import get_db
from models.product import (
    Category, Product, ProductOption, ProductQuickOption,
    ProductIngredient, ProductPreferenceSet, ProductPreferenceChoice,
)
from models.table import Table, TableGroup
from models.user import User
from routers.deps import require_manager

router = APIRouter()

EXPORT_VERSION = 1


def _serialize_product(p: Product) -> dict:
    quick_options = [
        {"name": qo.name, "price": qo.price, "allow_multiple": qo.allow_multiple,
         "sort_order": qo.sort_order, "is_favorite": qo.is_favorite,
         "favorite_sort_order": qo.favorite_sort_order, "is_compact": qo.is_compact}
        for qo in p.quick_options
    ]
    options = []
    for opt in p.options:
        sub = json.loads(opt.sub_choices) if opt.sub_choices else []
        options.append({"name": opt.name, "extra_cost": opt.extra_cost,
                        "allow_multiple": opt.allow_multiple, "sub_choices": sub,
                        "is_favorite": opt.is_favorite, "favorite_sort_order": opt.favorite_sort_order})
    ingredients = [
        {"name": ing.name, "extra_cost": ing.extra_cost,
         "is_favorite": ing.is_favorite, "favorite_sort_order": ing.favorite_sort_order}
        for ing in p.ingredients
    ]
    preference_sets = []
    for ps in p.preference_sets:
        shared = json.loads(ps.shared_subset) if ps.shared_subset else None
        default_index = None
        choices = []
        for i, ch in enumerate(ps.choices):
            if ch.id == ps.default_choice_id:
                default_index = i
            sub = json.loads(ch.sub_choices) if ch.sub_choices else []
            choices.append({"name": ch.name, "extra_cost": ch.extra_cost,
                            "sub_choices": sub, "disables_subset": ch.disables_subset})
        preference_sets.append({
            "name": ps.name, "choices": choices,
            "default_choice_index": default_index, "shared_subset": shared,
            "is_favorite": ps.is_favorite, "favorite_sort_order": ps.favorite_sort_order,
        })
    return {
        "name": p.name, "base_price": p.base_price, "is_available": p.is_available,
        "lifecycle_status": p.lifecycle_status, "sort_order": p.sort_order,
        "printer_zone_id": None,  # always stripped on export
        "quick_options": quick_options, "options": options,
        "ingredients": ingredients, "preference_sets": preference_sets,
    }


def _serialize_category(cat: Category) -> dict:
    products = [_serialize_product(p) for p in cat.products if p.lifecycle_status != "archived"]
    return {
        "name": cat.name, "color": cat.color, "sort_order": cat.sort_order,
        "parent_name": cat.parent.name if cat.parent else None,
        "general_sort_order": cat.general_sort_order, "auto_expanded": cat.auto_expanded,
        "products": products,
    }


@router.get("/export/catalog")
def export_catalog(db: Session = Depends(get_db), user: User = Depends(require_manager)):
    categories = db.query(Category).order_by(Category.sort_order).all()
    orphan_products = (
        db.query(Product)
        .filter(Product.category_id == None, Product.lifecycle_status != "archived")
        .order_by(Product.sort_order)
        .all()
    )
    data = {
        "categories": [_serialize_category(c) for c in categories],
        "uncategorized_products": [_serialize_product(p) for p in orphan_products],
    }
    payload = {
        "xenia_export_version": EXPORT_VERSION,
        "bundle": "catalog",
        "exported_at": datetime.now(timezone.utc).isoformat(),
        "data": data,
    }
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    return JSONResponse(
        content=payload,
        headers={"Content-Disposition": f'attachment; filename="xenia-catalog-{today}.json"'},
    )


@router.get("/export/tables")
def export_tables(db: Session = Depends(get_db), user: User = Depends(require_manager)):
    groups = db.query(TableGroup).order_by(TableGroup.sort_order).all()
    table_groups = []
    for g in groups:
        tables = [
            {"number": t.number, "label": t.label, "is_active": t.is_active,
             "floor_x": t.floor_x, "floor_y": t.floor_y}
            for t in sorted(g.tables, key=lambda t: t.number)
        ]
        table_groups.append({
            "name": g.name, "prefix": g.prefix, "sort_order": g.sort_order,
            "color": g.color, "tables": tables,
        })
    ungrouped = (
        db.query(Table)
        .filter(Table.group_id == None)
        .order_by(Table.number)
        .all()
    )
    ungrouped_tables = [
        {"number": t.number, "label": t.label, "is_active": t.is_active,
         "floor_x": t.floor_x, "floor_y": t.floor_y}
        for t in ungrouped
    ]
    payload = {
        "xenia_export_version": EXPORT_VERSION,
        "bundle": "tables",
        "exported_at": datetime.now(timezone.utc).isoformat(),
        "data": {"table_groups": table_groups, "ungrouped_tables": ungrouped_tables},
    }
    today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
    return JSONResponse(
        content=payload,
        headers={"Content-Disposition": f'attachment; filename="xenia-tables-{today}.json"'},
    )


@router.post("/import/catalog")
def import_catalog(payload: dict, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    if payload.get("bundle") != "catalog":
        raise HTTPException(status_code=400, detail="Wrong bundle type. Expected 'catalog'.")
    if payload.get("xenia_export_version", 0) != EXPORT_VERSION:
        raise HTTPException(status_code=400, detail="Unsupported export version.")

    data = payload.get("data", {})
    categories_data = data.get("categories", [])

    def _upsert_category(cat_data: dict, parent_id=None) -> Category:
        existing = db.query(Category).filter(
            Category.name == cat_data["name"],
            Category.parent_id == parent_id,
        ).first()
        if existing:
            existing.color = cat_data.get("color")
            existing.sort_order = cat_data.get("sort_order", 0)
            existing.general_sort_order = cat_data.get("general_sort_order", 0)
            existing.auto_expanded = cat_data.get("auto_expanded", False)
            db.flush()
            return existing
        else:
            cat = Category(
                name=cat_data["name"],
                color=cat_data.get("color"),
                sort_order=cat_data.get("sort_order", 0),
                parent_id=parent_id,
                general_sort_order=cat_data.get("general_sort_order", 0),
                auto_expanded=cat_data.get("auto_expanded", False),
            )
            db.add(cat)
            db.flush()
            return cat

    def _upsert_product(prod_data: dict, category_id=None):
        existing = db.query(Product).filter(Product.name == prod_data["name"]).first()
        if existing:
            existing.base_price = prod_data["base_price"]
            existing.is_available = prod_data.get("is_available", True)
            existing.lifecycle_status = prod_data.get("lifecycle_status", "active")
            existing.sort_order = prod_data.get("sort_order", 0)
            existing.category_id = category_id
            existing.printer_zone_id = None
            db.flush()
            product = existing
        else:
            product = Product(
                name=prod_data["name"],
                base_price=prod_data["base_price"],
                is_available=prod_data.get("is_available", True),
                lifecycle_status=prod_data.get("lifecycle_status", "active"),
                sort_order=prod_data.get("sort_order", 0),
                category_id=category_id,
                printer_zone_id=None,
            )
            db.add(product)
            db.flush()

        # Replace sub-items (safe: sub-items have no direct order history references)
        for qo in list(product.quick_options):
            db.delete(qo)
        for opt in list(product.options):
            db.delete(opt)
        for ing in list(product.ingredients):
            db.delete(ing)
        for ps in list(product.preference_sets):
            db.delete(ps)
        db.flush()

        for qo in prod_data.get("quick_options", []):
            db.add(ProductQuickOption(product_id=product.id, **qo))

        for opt in prod_data.get("options", []):
            sub_json = json.dumps(opt.get("sub_choices", []))
            db.add(ProductOption(
                product_id=product.id, name=opt["name"], extra_cost=opt.get("extra_cost", 0.0),
                allow_multiple=opt.get("allow_multiple", False), sub_choices=sub_json,
                is_favorite=opt.get("is_favorite", False),
                favorite_sort_order=opt.get("favorite_sort_order", 0),
            ))

        for ing in prod_data.get("ingredients", []):
            db.add(ProductIngredient(product_id=product.id, **ing))

        for ps_data in prod_data.get("preference_sets", []):
            shared_json = json.dumps(ps_data["shared_subset"]) if ps_data.get("shared_subset") else None
            ps = ProductPreferenceSet(
                product_id=product.id, name=ps_data["name"],
                shared_subset=shared_json,
                is_favorite=ps_data.get("is_favorite", False),
                favorite_sort_order=ps_data.get("favorite_sort_order", 0),
            )
            db.add(ps)
            db.flush()
            created_choices = []
            for ch in ps_data.get("choices", []):
                sub_json = json.dumps(ch.get("sub_choices", []))
                choice = ProductPreferenceChoice(
                    set_id=ps.id, name=ch["name"], extra_cost=ch.get("extra_cost", 0.0),
                    sub_choices=sub_json, disables_subset=ch.get("disables_subset", False),
                )
                db.add(choice)
                db.flush()
                created_choices.append(choice)
            idx = ps_data.get("default_choice_index")
            if idx is not None and 0 <= idx < len(created_choices):
                ps.default_choice_id = created_choices[idx].id

    # First pass: top-level categories (parent_name is None)
    for cat_data in categories_data:
        if cat_data.get("parent_name") is None:
            cat = _upsert_category(cat_data, parent_id=None)
            for prod_data in cat_data.get("products", []):
                _upsert_product(prod_data, category_id=cat.id)

    # Second pass: sub-categories (parent must already exist from first pass)
    for cat_data in categories_data:
        if cat_data.get("parent_name") is not None:
            parent = db.query(Category).filter(
                Category.name == cat_data["parent_name"],
                Category.parent_id == None,
            ).first()
            parent_id = parent.id if parent else None
            cat = _upsert_category(cat_data, parent_id=parent_id)
            for prod_data in cat_data.get("products", []):
                _upsert_product(prod_data, category_id=cat.id)

    # Uncategorized products (no category)
    for prod_data in data.get("uncategorized_products", []):
        _upsert_product(prod_data, category_id=None)

    db.commit()
    return {"ok": True, "message": "Catalog imported successfully."}


@router.post("/import/tables")
def import_tables(payload: dict, db: Session = Depends(get_db), user: User = Depends(require_manager)):
    if payload.get("bundle") != "tables":
        raise HTTPException(status_code=400, detail="Wrong bundle type. Expected 'tables'.")
    if payload.get("xenia_export_version", 0) != EXPORT_VERSION:
        raise HTTPException(status_code=400, detail="Unsupported export version.")

    data = payload.get("data", {})
    for group_data in data.get("table_groups", []):
        existing_group = db.query(TableGroup).filter(TableGroup.name == group_data["name"]).first()
        if existing_group:
            existing_group.prefix = group_data.get("prefix")
            existing_group.sort_order = group_data.get("sort_order", 0)
            existing_group.color = group_data.get("color")
            db.flush()
            group = existing_group
        else:
            group = TableGroup(
                name=group_data["name"],
                prefix=group_data.get("prefix"),
                sort_order=group_data.get("sort_order", 0),
                color=group_data.get("color"),
            )
            db.add(group)
            db.flush()

        for table_data in group_data.get("tables", []):
            existing_table = db.query(Table).filter(
                Table.number == table_data["number"],
                Table.group_id == group.id,
            ).first()
            if existing_table:
                existing_table.label = table_data.get("label")
                existing_table.is_active = table_data.get("is_active", True)
                existing_table.floor_x = table_data.get("floor_x")
                existing_table.floor_y = table_data.get("floor_y")
            else:
                db.add(Table(
                    number=table_data["number"],
                    label=table_data.get("label"),
                    group_id=group.id,
                    is_active=table_data.get("is_active", True),
                    floor_x=table_data.get("floor_x"),
                    floor_y=table_data.get("floor_y"),
                ))

    # Ungrouped tables (no zone)
    for table_data in data.get("ungrouped_tables", []):
        existing_table = db.query(Table).filter(
            Table.number == table_data["number"],
            Table.group_id == None,
        ).first()
        if existing_table:
            existing_table.label = table_data.get("label")
            existing_table.is_active = table_data.get("is_active", True)
            existing_table.floor_x = table_data.get("floor_x")
            existing_table.floor_y = table_data.get("floor_y")
        else:
            db.add(Table(
                number=table_data["number"],
                label=table_data.get("label"),
                group_id=None,
                is_active=table_data.get("is_active", True),
                floor_x=table_data.get("floor_x"),
                floor_y=table_data.get("floor_y"),
            ))

    db.commit()
    return {"ok": True, "message": "Tables imported successfully."}
