from __future__ import annotations

import unittest
from decimal import Decimal

from sqlalchemy import BigInteger, create_engine
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.models.base import Base  # noqa: E402
from app.models.master_data import Item, Warehouse  # noqa: E402
from app.models.operations import StockLot  # noqa: E402
from app.services.system_config import RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE, upsert_system_config  # noqa: E402
from app.services.operations import build_inventory_lot_no  # noqa: E402


class InventoryLotNumberingTest(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.material = Item(
            id=1,
            item_code="110501000185",
            item_name="左右后纵梁后段毛坯",
            item_type="RAW_MATERIAL",
            unit_weight_kg=Decimal("1"),
            status="ACTIVE",
        )
        self.other_material = Item(
            id=2,
            item_code="110501000186",
            item_name="其他原材料",
            item_type="RAW_MATERIAL",
            unit_weight_kg=Decimal("1"),
            status="ACTIVE",
        )
        self.warehouse = Warehouse(
            id=1,
            warehouse_code="WH-RAW",
            warehouse_name="原材料库",
            warehouse_type="RAW",
            status="ACTIVE",
        )
        self.db.add_all([self.material, self.other_material, self.warehouse])
        self.db.commit()

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

    def test_inventory_lot_no_starts_from_global_prefix_sequence(self) -> None:
        self.assertEqual(build_inventory_lot_no(self.db, self.material.id), "YL0001")

    def test_inventory_lot_no_increments_from_existing_lot_for_different_material(self) -> None:
        self.db.add(
            StockLot(
                lot_no="YL0001",
                lot_role="INBOUND_RAW",
                item_id=self.other_material.id,
                warehouse_id=self.warehouse.id,
                source_doc_type="TEST",
                source_doc_id=1,
                inbound_qty=Decimal("0"),
                inbound_weight_kg=Decimal("100"),
                remaining_qty=Decimal("0"),
                remaining_weight_kg=Decimal("100"),
                locked_qty=Decimal("0"),
                locked_weight_kg=Decimal("0"),
                unit_cost=Decimal("5"),
                quality_status="PASS",
                status="AVAILABLE",
            )
        )
        self.db.commit()

        self.assertEqual(build_inventory_lot_no(self.db, self.material.id), "YL0002")

    def test_inventory_lot_no_uses_natural_width_after_four_digits(self) -> None:
        self.db.add(
            StockLot(
                lot_no="YL9999",
                lot_role="INBOUND_RAW",
                item_id=self.material.id,
                warehouse_id=self.warehouse.id,
                source_doc_type="TEST",
                source_doc_id=1,
                inbound_qty=Decimal("0"),
                inbound_weight_kg=Decimal("100"),
                remaining_qty=Decimal("0"),
                remaining_weight_kg=Decimal("100"),
                locked_qty=Decimal("0"),
                locked_weight_kg=Decimal("0"),
                unit_cost=Decimal("5"),
                quality_status="PASS",
                status="AVAILABLE",
            )
        )
        self.db.commit()

        self.assertEqual(build_inventory_lot_no(self.db, self.material.id), "YL10000")

    def test_inventory_lot_no_uses_configured_prefix(self) -> None:
        upsert_system_config(
            self.db,
            config_code=RAW_MATERIAL_LOT_PREFIX_CONFIG_CODE,
            config_name="原材料库存批次号前缀",
            config_value="rm",
        )
        self.db.commit()

        self.assertEqual(build_inventory_lot_no(self.db, self.material.id), "RM0001")
