import json
import uuid
from decimal import Decimal, InvalidOperation
from typing import Any, Dict, List, Optional

from sqlalchemy import text

from app.db import engine


_PRODUCT_SCHEMA_READY = False


DEFAULT_PRODUCTS = [
    {
        "sku": "EV-AC-7KW",
        "jasmin_sales_item": "CARREGADOR_MONO_7KW",
        "name": "Carregador EV 7.4kW monofásico",
        "category": "Carregadores",
        "description": "Carregador AC monofásico para moradias e pequenas instalações.",
        "default_unit_price": "590.00",
        "vat_rate": "23.00",
    },
    {
        "sku": "EV-AC-22KW",
        "jasmin_sales_item": "CARREGADOR_TRIF_22KW",
        "name": "Carregador EV 22kW trifásico",
        "category": "Carregadores",
        "description": "Carregador AC trifásico para empresas, condomínios e instalações com maior potência.",
        "default_unit_price": "790.00",
        "vat_rate": "23.00",
    },
    {
        "sku": "CAB-T2-5M",
        "jasmin_sales_item": "CABO_VE",
        "name": "Cabo Type 2 5m",
        "category": "Acessórios",
        "description": "Cabo Type 2 para carregamento de veículos elétricos.",
        "default_unit_price": "120.00",
        "vat_rate": "23.00",
    },
    {
        "sku": "CAB-T2-7M",
        "jasmin_sales_item": "CABO_VE",
        "name": "Cabo Type 2 7m",
        "category": "Acessórios",
        "description": "Cabo Type 2 de 7 metros para carregamento de veículos elétricos.",
        "default_unit_price": "150.00",
        "vat_rate": "23.00",
    },
    {
        "sku": "INST-BASIC",
        "jasmin_sales_item": "INSTALACAO",
        "name": "Instalação básica",
        "category": "Serviços",
        "description": "Serviço de instalação básica sujeito a validação técnica.",
        "default_unit_price": "350.00",
        "vat_rate": "23.00",
    },
    {
        "sku": "SHIP-STD",
        "jasmin_sales_item": "TRANSPORTE",
        "name": "Transporte nacional",
        "category": "Serviços",
        "description": "Envio nacional standard.",
        "default_unit_price": "15.00",
        "vat_rate": "23.00",
    },
]


def _json(value: Any) -> str:
    return json.dumps(value or {}, ensure_ascii=False, default=str)


def _uuid_or_none(value: Optional[str]) -> Optional[str]:
    value = str(value or "").strip()
    return value or None


def _decimal(value: Any, default: str = "0") -> Decimal:
    try:
        if value is None or str(value).strip() == "":
            return Decimal(default)
        return Decimal(str(value).replace(",", ".").strip())
    except (InvalidOperation, ValueError):
        return Decimal(default)


def _money(value: Any) -> str:
    return f"{_decimal(value):.2f}"


def _strict_decimal(value: Any, field_name: str) -> Decimal:
    try:
        if value is None or str(value).strip() == "":
            raise InvalidOperation()
        return Decimal(str(value).replace(",", ".").strip())
    except (InvalidOperation, ValueError):
        raise ValueError(f"{field_name} inválido.")


def _non_negative_decimal(value: Any, field_name: str, default: str = "0") -> Decimal:
    if value is None or str(value).strip() == "":
        value = default
    d = _strict_decimal(value, field_name)
    if d < 0:
        raise ValueError(f"{field_name} não pode ser negativo.")
    return d


def _positive_decimal(value: Any, field_name: str, default: str = "1") -> Decimal:
    if value is None or str(value).strip() == "":
        value = default
    d = _strict_decimal(value, field_name)
    if d <= 0:
        raise ValueError(f"{field_name} deve ser superior a zero.")
    return d


def _bool(value: Any) -> bool:
    return str(value or "").lower() in {"1", "true", "yes", "on", "sim", "ativo"}


def ensure_product_schema(*, seed: bool = True) -> None:
    """Cria catálogo simples de produtos e tabelas de linhas.

    Sem variantes: cada produto/preço configurável fica numa linha em `products`.
    As linhas de oportunidade/encomenda copiam nome, SKU e preço no momento para
    preservar histórico quando o catálogo for alterado.
    """
    global _PRODUCT_SCHEMA_READY
    if _PRODUCT_SCHEMA_READY:
        return

    with engine.begin() as conn:
        conn.execute(text("""
            CREATE TABLE IF NOT EXISTS products (
                id UUID PRIMARY KEY,
                sku TEXT UNIQUE NOT NULL,
                jasmin_sales_item TEXT,
                name TEXT NOT NULL,
                category TEXT NOT NULL DEFAULT 'Geral',
                description TEXT,
                default_unit_price NUMERIC(12,2) NOT NULL DEFAULT 0,
                vat_rate NUMERIC(5,2) NOT NULL DEFAULT 23,
                active BOOLEAN NOT NULL DEFAULT TRUE,
                metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
                created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
                updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
            )
        """))
        conn.execute(text("""
            CREATE TABLE IF NOT EXISTS opportunity_items (
                id UUID PRIMARY KEY,
                opportunity_id UUID NOT NULL REFERENCES opportunities(id) ON DELETE CASCADE,
                product_id UUID REFERENCES products(id) ON DELETE SET NULL,
                sku TEXT,
                jasmin_sales_item TEXT,
                product_name TEXT NOT NULL,
                description TEXT,
                quantity NUMERIC(12,2) NOT NULL DEFAULT 1,
                unit_price NUMERIC(12,2) NOT NULL DEFAULT 0,
                discount_amount NUMERIC(12,2) NOT NULL DEFAULT 0,
                total_price NUMERIC(12,2) NOT NULL DEFAULT 0,
                status TEXT NOT NULL DEFAULT 'INTERESTED',
                metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
                created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
                updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
            )
        """))
        conn.execute(text("""
            CREATE TABLE IF NOT EXISTS order_items (
                id UUID PRIMARY KEY,
                order_id UUID,
                opportunity_id UUID REFERENCES opportunities(id) ON DELETE SET NULL,
                opportunity_item_id UUID REFERENCES opportunity_items(id) ON DELETE SET NULL,
                product_id UUID REFERENCES products(id) ON DELETE SET NULL,
                sku TEXT,
                jasmin_sales_item TEXT,
                product_name TEXT NOT NULL,
                description TEXT,
                quantity NUMERIC(12,2) NOT NULL DEFAULT 1,
                unit_price NUMERIC(12,2) NOT NULL DEFAULT 0,
                total_price NUMERIC(12,2) NOT NULL DEFAULT 0,
                fulfillment_status TEXT NOT NULL DEFAULT 'PENDING',
                metadata JSONB NOT NULL DEFAULT '{}'::jsonb,
                created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
                updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
            )
        """))
        for stmt in [
            "ALTER TABLE products ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT",
            "ALTER TABLE opportunity_items ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT",
            "ALTER TABLE order_items ADD COLUMN IF NOT EXISTS jasmin_sales_item TEXT",
        ]:
            conn.execute(text(stmt))

        conn.execute(text("CREATE INDEX IF NOT EXISTS idx_products_category ON products(category)"))
        conn.execute(text("CREATE INDEX IF NOT EXISTS idx_products_active ON products(active)"))
        conn.execute(text("CREATE INDEX IF NOT EXISTS idx_opportunity_items_opp ON opportunity_items(opportunity_id)"))
        conn.execute(text("CREATE INDEX IF NOT EXISTS idx_order_items_opp ON order_items(opportunity_id)"))

        if seed:
            count = conn.execute(text("SELECT count(*) FROM products")).scalar() or 0
            if int(count) == 0:
                for product in DEFAULT_PRODUCTS:
                    conn.execute(text("""
                        INSERT INTO products (
                            id, sku, jasmin_sales_item, name, category, description,
                            default_unit_price, vat_rate, active, metadata
                        ) VALUES (
                            CAST(:id AS UUID), :sku, :jasmin_sales_item, :name, :category, :description,
                            :default_unit_price, :vat_rate, TRUE, CAST(:metadata AS JSONB)
                        )
                        ON CONFLICT (sku) DO NOTHING
                    """), {
                        "id": str(uuid.uuid4()),
                        "sku": product["sku"],
                        "jasmin_sales_item": product.get("jasmin_sales_item"),
                        "name": product["name"],
                        "category": product["category"],
                        "description": product["description"],
                        "default_unit_price": product["default_unit_price"],
                        "vat_rate": product["vat_rate"],
                        "metadata": _json({"seed": True}),
                    })

    _PRODUCT_SCHEMA_READY = True


def list_product_categories() -> List[str]:
    ensure_product_schema()
    with engine.begin() as conn:
        rows = conn.execute(text("""
            SELECT DISTINCT category
            FROM products
            WHERE COALESCE(category, '') <> ''
            ORDER BY category
        """)).all()
    return [str(row[0]) for row in rows]


def list_products(
    *,
    q: Optional[str] = None,
    category: Optional[str] = None,
    active: Optional[str] = "true",
    limit: int = 300,
) -> List[Dict[str, Any]]:
    ensure_product_schema()
    filters = []
    params: Dict[str, Any] = {"limit": int(limit)}
    if q:
        filters.append("(sku ILIKE :q OR COALESCE(jasmin_sales_item, '') ILIKE :q OR name ILIKE :q OR category ILIKE :q OR COALESCE(description, '') ILIKE :q)")
        params["q"] = f"%{str(q).strip()}%"
    if category and category != "all":
        filters.append("category = :category")
        params["category"] = category
    if active in {"true", "false"}:
        filters.append("active = :active")
        params["active"] = active == "true"
    where_sql = "WHERE " + " AND ".join(filters) if filters else ""
    sql = text(f"""
        SELECT
            id::text,
            sku,
            jasmin_sales_item,
            name,
            category,
            description,
            default_unit_price,
            vat_rate,
            active,
            created_at,
            updated_at
        FROM products
        {where_sql}
        ORDER BY active DESC, category ASC, name ASC
        LIMIT :limit
    """)
    with engine.begin() as conn:
        rows = conn.execute(sql, params).mappings().all()
    return [dict(row) for row in rows]


def get_product(product_id: str) -> Optional[Dict[str, Any]]:
    ensure_product_schema()
    sql = text("""
        SELECT
            id::text,
            sku,
            jasmin_sales_item,
            name,
            category,
            description,
            default_unit_price,
            vat_rate,
            active,
            metadata,
            created_at,
            updated_at
        FROM products
        WHERE id = CAST(:product_id AS UUID)
        LIMIT 1
    """)
    with engine.begin() as conn:
        row = conn.execute(sql, {"product_id": product_id}).mappings().first()
    return dict(row) if row else None


def create_product(data: Dict[str, Any]) -> str:
    ensure_product_schema()
    product_id = str(uuid.uuid4())
    sku = str(data.get("sku") or "").strip().upper()
    name = str(data.get("name") or "").strip()
    if not sku:
        raise ValueError("SKU é obrigatório.")
    if not name:
        raise ValueError("Nome do produto é obrigatório.")
    default_unit_price = _non_negative_decimal(data.get("default_unit_price", data.get("price")), "Preço base")
    vat_rate = _non_negative_decimal(data.get("vat_rate", "23"), "IVA")
    with engine.begin() as conn:
        conn.execute(text("""
            INSERT INTO products (
                id, sku, jasmin_sales_item, name, category, description,
                default_unit_price, vat_rate, active, metadata
            ) VALUES (
                CAST(:id AS UUID), :sku, :jasmin_sales_item, :name, :category, :description,
                :default_unit_price, :vat_rate, :active, CAST(:metadata AS JSONB)
            )
        """), {
            "id": product_id,
            "sku": sku,
            "jasmin_sales_item": str(data.get("jasmin_sales_item") or "").strip().upper() or None,
            "name": name,
            "category": str(data.get("category") or "Geral").strip() or "Geral",
            "description": str(data.get("description") or "").strip(),
            "default_unit_price": _money(default_unit_price),
            "vat_rate": _money(vat_rate),
            "active": _bool(data.get("active", "true")),
            "metadata": _json({}),
        })
    return product_id


def update_product(product_id: str, data: Dict[str, Any]) -> bool:
    ensure_product_schema()
    sku = str(data.get("sku") or "").strip().upper()
    name = str(data.get("name") or "").strip()
    if not sku:
        raise ValueError("SKU é obrigatório.")
    if not name:
        raise ValueError("Nome do produto é obrigatório.")
    default_unit_price = _non_negative_decimal(data.get("default_unit_price", data.get("price")), "Preço base")
    vat_rate = _non_negative_decimal(data.get("vat_rate", "23"), "IVA")
    with engine.begin() as conn:
        result = conn.execute(text("""
            UPDATE products
            SET sku = :sku,
                jasmin_sales_item = :jasmin_sales_item,
                name = :name,
                category = :category,
                description = :description,
                default_unit_price = :default_unit_price,
                vat_rate = :vat_rate,
                active = :active,
                updated_at = now()
            WHERE id = CAST(:product_id AS UUID)
        """), {
            "product_id": product_id,
            "sku": sku,
            "jasmin_sales_item": str(data.get("jasmin_sales_item") or "").strip().upper() or None,
            "name": name,
            "category": str(data.get("category") or "Geral").strip() or "Geral",
            "description": str(data.get("description") or "").strip(),
            "default_unit_price": _money(default_unit_price),
            "vat_rate": _money(vat_rate),
            "active": _bool(data.get("active")),
        })
    return result.rowcount > 0


def set_product_active(product_id: str, active: bool) -> bool:
    ensure_product_schema()
    with engine.begin() as conn:
        result = conn.execute(text("""
            UPDATE products
            SET active = :active,
                updated_at = now()
            WHERE id = CAST(:product_id AS UUID)
        """), {"product_id": product_id, "active": bool(active)})
    return result.rowcount > 0


def list_opportunity_items(opportunity_id: str) -> List[Dict[str, Any]]:
    ensure_product_schema()
    sql = text("""
        SELECT
            oi.id::text,
            oi.opportunity_id::text,
            oi.product_id::text,
            oi.sku,
            COALESCE(NULLIF(oi.jasmin_sales_item, ''), p.jasmin_sales_item) AS jasmin_sales_item,
            oi.product_name,
            oi.description,
            oi.quantity,
            oi.unit_price,
            oi.discount_amount,
            oi.total_price,
            oi.status,
            oi.metadata,
            oi.created_at,
            oi.updated_at,
            p.active AS product_active
        FROM opportunity_items oi
        LEFT JOIN products p ON p.id = oi.product_id
        WHERE oi.opportunity_id = CAST(:opportunity_id AS UUID)
        ORDER BY oi.created_at ASC
    """)
    with engine.begin() as conn:
        rows = conn.execute(sql, {"opportunity_id": opportunity_id}).mappings().all()
    return [dict(row) for row in rows]


def _recalculate_opportunity_value(conn, opportunity_id: str) -> None:
    conn.execute(text("""
        UPDATE opportunities
        SET value_amount = COALESCE((
                SELECT SUM(total_price)
                FROM opportunity_items
                WHERE opportunity_id = CAST(:opportunity_id AS UUID)
                  AND status NOT IN ('REJECTED', 'CANCELLED')
            ), 0),
            updated_at = now()
        WHERE id = CAST(:opportunity_id AS UUID)
    """), {"opportunity_id": opportunity_id})


def add_opportunity_item(
    opportunity_id: str,
    *,
    product_id: Optional[str] = None,
    product_name: Optional[str] = None,
    sku: Optional[str] = None,
    jasmin_sales_item: Optional[str] = None,
    description: Optional[str] = None,
    quantity: Any = "1",
    unit_price: Any = None,
    discount_amount: Any = "0",
    status: str = "INTERESTED",
) -> str:
    ensure_product_schema()
    item_id = str(uuid.uuid4())
    product: Optional[Dict[str, Any]] = get_product(product_id) if product_id else None
    name = str(product_name or (product or {}).get("name") or "").strip()
    if not name:
        raise ValueError("Produto é obrigatório.")
    q = _positive_decimal(quantity, "Quantidade", "1")
    price = _non_negative_decimal(unit_price if unit_price not in {None, ""} else (product or {}).get("default_unit_price"), "Preço unitário", "0")
    discount = _non_negative_decimal(discount_amount, "Desconto", "0")
    total = (q * price) - discount
    if total < 0:
        raise ValueError("Total da linha não pode ser negativo.")
    normalized_status = str(status or "INTERESTED").strip().upper()

    with engine.begin() as conn:
        conn.execute(text("""
            INSERT INTO opportunity_items (
                id, opportunity_id, product_id, sku, jasmin_sales_item, product_name, description,
                quantity, unit_price, discount_amount, total_price, status, metadata
            ) VALUES (
                CAST(:id AS UUID), CAST(:opportunity_id AS UUID), CAST(:product_id AS UUID),
                :sku, :jasmin_sales_item, :product_name, :description,
                :quantity, :unit_price, :discount_amount, :total_price, :status, CAST(:metadata AS JSONB)
            )
        """), {
            "id": item_id,
            "opportunity_id": opportunity_id,
            "product_id": _uuid_or_none(product_id),
            "sku": sku or (product or {}).get("sku") or "",
            "jasmin_sales_item": (jasmin_sales_item or (product or {}).get("jasmin_sales_item") or "").strip().upper() or None,
            "product_name": name,
            "description": description if description is not None else (product or {}).get("description") or "",
            "quantity": _money(q),
            "unit_price": _money(price),
            "discount_amount": _money(discount),
            "total_price": _money(total),
            "status": normalized_status,
            "metadata": _json({"source": "admin", "jasmin_sales_item": (jasmin_sales_item or (product or {}).get("jasmin_sales_item") or "").strip().upper()}),
        })
        _recalculate_opportunity_value(conn, opportunity_id)
        conn.execute(text("""
            INSERT INTO opportunity_events (
                id, opportunity_id, event_type, note, payload, created_by
            ) VALUES (
                CAST(:id AS UUID), CAST(:opportunity_id AS UUID), 'opportunity_item_added',
                :note, CAST(:payload AS JSONB), 'operator'
            )
        """), {
            "id": str(uuid.uuid4()),
            "opportunity_id": opportunity_id,
            "note": f"Produto adicionado: {name} x {_money(q)}",
            "payload": _json({"item_id": item_id, "sku": sku or (product or {}).get("sku") or "", "jasmin_sales_item": (jasmin_sales_item or (product or {}).get("jasmin_sales_item") or "").strip().upper()}),
        })
    return item_id


def update_opportunity_item(item_id: str, data: Dict[str, Any]) -> Optional[str]:
    ensure_product_schema()
    q = _positive_decimal(data.get("quantity"), "Quantidade", "1")
    price = _non_negative_decimal(data.get("unit_price"), "Preço unitário", "0")
    discount = _non_negative_decimal(data.get("discount_amount"), "Desconto", "0")
    total = (q * price) - discount
    if total < 0:
        raise ValueError("Total da linha não pode ser negativo.")
    status = str(data.get("status") or "INTERESTED").strip().upper()
    with engine.begin() as conn:
        row = conn.execute(text("""
            UPDATE opportunity_items
            SET jasmin_sales_item = COALESCE(NULLIF(:jasmin_sales_item, ''), jasmin_sales_item),
                quantity = :quantity,
                unit_price = :unit_price,
                discount_amount = :discount_amount,
                total_price = :total_price,
                status = :status,
                updated_at = now()
            WHERE id = CAST(:item_id AS UUID)
            RETURNING opportunity_id::text
        """), {
            "item_id": item_id,
            "jasmin_sales_item": str(data.get("jasmin_sales_item") or "").strip().upper(),
            "quantity": _money(q),
            "unit_price": _money(price),
            "discount_amount": _money(discount),
            "total_price": _money(total),
            "status": status,
        }).first()
        if row:
            _recalculate_opportunity_value(conn, str(row[0]))
            return str(row[0])
    return None


def delete_opportunity_item(item_id: str) -> Optional[str]:
    ensure_product_schema()
    with engine.begin() as conn:
        row = conn.execute(text("""
            DELETE FROM opportunity_items
            WHERE id = CAST(:item_id AS UUID)
            RETURNING opportunity_id::text, product_name
        """), {"item_id": item_id}).first()
        if row:
            opportunity_id = str(row[0])
            _recalculate_opportunity_value(conn, opportunity_id)
            conn.execute(text("""
                INSERT INTO opportunity_events (
                    id, opportunity_id, event_type, note, payload, created_by
                ) VALUES (
                    CAST(:id AS UUID), CAST(:opportunity_id AS UUID), 'opportunity_item_deleted',
                    :note, '{}'::jsonb, 'operator'
                )
            """), {
                "id": str(uuid.uuid4()),
                "opportunity_id": opportunity_id,
                "note": f"Produto removido: {row[1] or 'produto'}",
            })
            return opportunity_id
    return None
