from __future__ import annotations

import unittest
from datetime import date
from decimal import Decimal

from fastapi import HTTPException
from sqlalchemy import BigInteger, create_engine, select
from sqlalchemy.ext.compiler import compiles
from sqlalchemy.orm import Session, sessionmaker


@compiles(BigInteger, "sqlite")
def _compile_bigint_for_sqlite(type_, compiler, **kw) -> str:
    _ = type_, compiler, kw
    return "INTEGER"


import app.models.master_data  # noqa: E402,F401
import app.models.miniapp  # noqa: E402,F401
import app.models.operations  # noqa: E402,F401
import app.models.org  # noqa: E402,F401
import app.models.planning  # noqa: E402,F401
import app.models.sales  # noqa: E402,F401
from app.api.routes.purchase import create_purchase_order, update_purchase_order  # noqa: E402
from app.models.base import Base  # noqa: E402
from app.models.master_data import Item, Material  # noqa: E402
from app.models.operations import PurchaseOrder, PurchaseOrderItem, PurchaseOrderSalesOrderLink, Supplier  # noqa: E402
from app.models.sales import Customer, SalesOrder, SalesOrderItem  # noqa: E402
from app.schemas.operations import PurchaseOrderCreate, PurchaseOrderItemCreate, PurchaseOrderUpdate  # noqa: E402


class PurchaseOrderMultiSalesLinksTest(unittest.TestCase):
    def setUp(self) -> None:
        engine = create_engine("sqlite+pysqlite:///:memory:", future=True)
        Base.metadata.create_all(engine)
        self.SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, future=True)
        self.db: Session = self.SessionLocal()
        self.supplier = Supplier(id=1, supplier_code="SUP-MULTI-001", supplier_name="多订单供应商", short_name="多订单供应商", lead_time_days=0, default_tax_rate=Decimal("0"), status="ACTIVE")
        self.customer = Customer(id=1, customer_code="CUS-MULTI-001", customer_name="多订单客户", short_name="客户", status="ACTIVE")
        self.product = Item(id=1, item_code="FG-MULTI-001", item_name="多订单成品", item_type="FINISHED_GOOD", unit_weight_kg=Decimal("1"), status="ACTIVE")
        self.material = Item(id=2, item_code="RM-MULTI-001", item_name="多订单原料", item_type="RAW_MATERIAL", unit_weight_kg=Decimal("1"), status="ACTIVE")
        self.db.add_all([self.supplier, self.customer, self.product, self.material])
        self.db.flush()
        self.db.add(Material(item_id=self.material.id, default_supplier_id=self.supplier.id, purchase_calc_mode="BY_WEIGHT", min_purchase_qty=Decimal("0"), purchase_multiple_qty=Decimal("0"), purchase_multiple_weight_kg=Decimal("0"), lead_time_days=0))
        self.order1 = SalesOrder(id=1, order_no="销售2026-00001", customer_id=self.customer.id, order_date=date.today(), status="OPEN")
        self.order2 = SalesOrder(id=2, order_no="销售2026-00002", customer_id=self.customer.id, order_date=date.today(), status="OPEN")
        self.item1 = SalesOrderItem(id=1, sales_order_id=1, line_no=1, product_item_id=self.product.id, order_qty=Decimal("100"), delivered_qty=Decimal("0"), unit_price=Decimal("10"), line_amount=Decimal("1000"), status="OPEN")
        self.item2 = SalesOrderItem(id=2, sales_order_id=2, line_no=1, product_item_id=self.product.id, order_qty=Decimal("200"), delivered_qty=Decimal("0"), unit_price=Decimal("10"), line_amount=Decimal("2000"), status="OPEN")
        self.db.add_all([self.order1, self.order2, self.item1, self.item2])
        self.db.commit()

    def tearDown(self) -> None:
        self.db.close()

    def test_purchase_order_can_link_multiple_sales_orders(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                target_warehouse_type="RAW",
                sales_order_ids=[self.order1.id, self.order2.id],
                items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("300"), unit_price=Decimal("6"))],
            ),
            db=self.db,
        )

        links = self.db.scalars(select(PurchaseOrderSalesOrderLink).order_by(PurchaseOrderSalesOrderLink.sales_order_id)).all()
        self.assertEqual([link.sales_order_id for link in links], [self.order1.id, self.order2.id])
        self.assertEqual(result.source_type, "SALES_ORDER")
        self.assertEqual(result.source_order_nos, "销售2026-00001、销售2026-00002")

    def test_purchase_order_recreates_missing_sales_order_link_table(self) -> None:
        PurchaseOrderSalesOrderLink.__table__.drop(self.db.get_bind())

        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                target_warehouse_type="RAW",
                sales_order_ids=[self.order1.id],
                items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("300"), unit_price=Decimal("6"))],
            ),
            db=self.db,
        )

        links = self.db.scalars(select(PurchaseOrderSalesOrderLink)).all()
        self.assertEqual(result.source_type, "SALES_ORDER")
        self.assertEqual([link.sales_order_id for link in links], [self.order1.id])

    def test_purchase_order_update_replaces_sales_order_links(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                target_warehouse_type="RAW",
                sales_order_ids=[self.order1.id, self.order2.id],
                items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("300"), unit_price=Decimal("6"))],
            ),
            db=self.db,
        )

        updated = update_purchase_order(
            result.purchase_order_id,
            PurchaseOrderUpdate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                target_warehouse_type="RAW",
                sales_order_ids=[self.order2.id],
                items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("250"), unit_price=Decimal("6"))],
            ),
            db=self.db,
        )

        links = self.db.scalars(select(PurchaseOrderSalesOrderLink).order_by(PurchaseOrderSalesOrderLink.sales_order_id)).all()
        self.assertEqual([link.sales_order_id for link in links], [self.order2.id])
        self.assertEqual(updated.source_type, "SALES_ORDER")
        self.assertEqual(updated.source_order_nos, "销售2026-00002")

    def test_purchase_order_update_missing_sales_order_keeps_existing_links_visible(self) -> None:
        result = create_purchase_order(
            PurchaseOrderCreate(
                supplier_id=self.supplier.id,
                expected_date=date.today(),
                target_warehouse_type="RAW",
                sales_order_ids=[self.order1.id],
                items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("300"), unit_price=Decimal("6"))],
            ),
            db=self.db,
        )

        with self.assertRaises(HTTPException) as exc:
            update_purchase_order(
                result.purchase_order_id,
                PurchaseOrderUpdate(
                    supplier_id=self.supplier.id,
                    expected_date=date.today(),
                    target_warehouse_type="RAW",
                    sales_order_ids=[999],
                    items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("250"), unit_price=Decimal("6"))],
                ),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 404)
        links = self.db.scalars(select(PurchaseOrderSalesOrderLink).where(PurchaseOrderSalesOrderLink.purchase_order_id == result.purchase_order_id)).all()
        self.assertEqual([link.sales_order_id for link in links], [self.order1.id])
        order = self.db.get(PurchaseOrder, result.purchase_order_id)
        self.assertEqual(float(order.total_amount), 1800)
        items = self.db.scalars(select(PurchaseOrderItem).where(PurchaseOrderItem.purchase_order_id == result.purchase_order_id)).all()
        self.assertEqual([float(item.order_weight_kg) for item in items], [300])

    def test_purchase_order_rejects_non_positive_sales_order_ids(self) -> None:
        with self.assertRaises(HTTPException) as exc:
            create_purchase_order(
                PurchaseOrderCreate(
                    supplier_id=self.supplier.id,
                    expected_date=date.today(),
                    target_warehouse_type="RAW",
                    sales_order_ids=[0],
                    items=[PurchaseOrderItemCreate(material_item_id=self.material.id, order_weight_kg=Decimal("300"), unit_price=Decimal("6"))],
                ),
                db=self.db,
            )

        self.assertEqual(exc.exception.status_code, 400)
