from datetime import date
from io import BytesIO

from openpyxl import Workbook
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session, sessionmaker

from app.db.session import Base
from app.models.mix import Mix
from app.models.product import Product
from app.models.raw_material import RawMaterial
from app.models.throughput import ProductionThroughput, ThroughputProduct
from app.seed import seed_throughput_products_from_costing
from app.services.throughput_service import (
    calculate_kg,
    import_entries_from_file,
    import_names_sheet,
    import_production_sheet,
    normalise_staff_name,
    qa_passed,
)


def _session() -> Session:
    engine = create_engine("sqlite:///:memory:")
    Base.metadata.create_all(bind=engine)
    return sessionmaker(bind=engine, expire_on_commit=False)()


def _costing_mix(db: Session, tenant_id: str = "hunter-premium-produce") -> Mix:
    raw_material = RawMaterial(
        tenant_id=tenant_id,
        name="Maize",
        unit_of_measure="tonne",
        kg_per_unit=1000,
        status="active",
    )
    db.add(raw_material)
    db.flush()
    mix = Mix(tenant_id=tenant_id, client_name="Hunter", name="Maize Mix")
    db.add(mix)
    db.flush()
    return mix


def test_calculate_kg_bags():
    assert calculate_kg(10, "bags", 20) == 200.0


def test_calculate_kg_kg_ignores_bag_size():
    assert calculate_kg(550, "kg", None) == 550.0
    assert calculate_kg(550, "kg", 20) == 550.0


def test_calculate_kg_zero_quantity():
    assert calculate_kg(0, "bags", 20) == 0.0
    assert calculate_kg(None, "bags", 20) == 0.0


def test_staff_name_normalisation():
    assert normalise_staff_name("  Jake ") == "Jake"
    assert normalise_staff_name("jake  smith") == "jake smith"
    assert normalise_staff_name("") is None
    assert normalise_staff_name(None) is None


def test_qa_passed_flag():
    entry = ProductionThroughput(
        production_date=date(2026, 1, 1),
        product_name_snapshot="X",
        quantity=1,
        quantity_type="bags",
        scales_checked=True,
        label_correct=True,
        bag_sealed=True,
        pallet_good_condition=True,
    )
    assert qa_passed(entry) is True
    entry.bag_sealed = False
    assert qa_passed(entry) is False


def _make_workbook() -> BytesIO:
    wb = Workbook()
    names = wb.active
    names.title = "Names"
    names.append(["Name", "Item ID"])
    names.append(["Whole Wheat 20kg", 1001])
    names.append(["Bulka Maize", 1002])

    production = wb.create_sheet("Production")
    production.append(["#VALUE!", "Operations Throughput"])
    production.append([None] * 8 + ["TEST WEIGHT"])
    production.append([
        "DATE", "GRAIN", "BAG SIZE", "SCALES", "LABEL", "SEALED", "PALLET",
        "BOX", 1, 2, 3, 4, 5, "QTY", "STAFF", "NOTES",
    ])
    production.append([date(2026, 4, 1), "Whole Wheat 20kg", 20, True, True, True, True, None, None, None, None, None, None, 100, "  Jake  ", None])
    production.append([date(2026, 4, 1), "Bulka Maize", None, True, True, False, True, "B7", None, None, None, None, None, 1500, "Alex", "ok"])
    production.append([date(2026, 4, 2), "Whole Wheat 20kg", 20, False, True, True, True, None, None, None, None, None, None, 50, "Jake", None])

    buf = BytesIO()
    wb.save(buf)
    buf.seek(0)
    return buf


def test_import_names_and_production():
    from openpyxl import load_workbook

    db = _session()
    wb = load_workbook(_make_workbook(), data_only=True)
    created, _ = import_names_sheet(db, wb, "test-tenant")
    assert created == 2

    imported, skipped = import_production_sheet(db, wb, "test-tenant")
    assert imported == 3
    assert skipped == 0

    entries = db.scalars(select(ProductionThroughput).order_by(ProductionThroughput.id)).all()
    bags_entry = entries[0]
    assert bags_entry.quantity_type == "bags"
    assert bags_entry.calculated_kg == 2000.0
    assert bags_entry.staff_name == "Jake"  # whitespace trimmed

    bulka_entry = entries[1]
    assert bulka_entry.quantity_type == "kg"
    assert bulka_entry.calculated_kg == 1500.0
    assert qa_passed(bulka_entry) is False  # bag_sealed was False

    # Product master should have absorbed default_bag_size for the wheat product
    wheat = db.scalar(
        select(ThroughputProduct).where(ThroughputProduct.name == "Whole Wheat 20kg")
    )
    assert wheat is not None
    assert wheat.default_bag_size == 20


def test_product_name_snapshot_preserved_when_product_renamed():
    db = _session()
    product = ThroughputProduct(tenant_id="t", name="Original Name", default_bag_size=20)
    db.add(product)
    db.flush()
    entry = ProductionThroughput(
        tenant_id="t",
        production_date=date(2026, 4, 1),
        product_id=product.id,
        product_name_snapshot=product.name,
        bag_size=20,
        quantity=10,
        quantity_type="bags",
        calculated_kg=200,
    )
    db.add(entry)
    db.flush()

    product.name = "Renamed Product"
    db.flush()

    reloaded = db.scalar(select(ProductionThroughput).where(ProductionThroughput.id == entry.id))
    assert reloaded.product_name_snapshot == "Original Name"


def test_seed_throughput_products_from_costing_products():
    db = _session()
    mix = _costing_mix(db)
    db.add_all(
        [
            Product(
                tenant_id="hunter-premium-produce",
                client_name="Hunter",
                item_id="1001",
                name="Whole Wheat 20kg",
                mix_id=mix.id,
                sale_type="standard",
                unit_of_measure="20kg bag",
                visible=True,
            ),
            Product(
                tenant_id="hunter-premium-produce",
                client_name="Hunter",
                item_id="1002",
                name="Bulka Maize",
                mix_id=mix.id,
                sale_type="bulka",
                unit_of_measure="tonne",
                visible=False,
            ),
        ]
    )
    db.flush()

    report = seed_throughput_products_from_costing(db)

    assert report == {"created": 2, "updated": 0, "skipped": 0}
    products = db.scalars(select(ThroughputProduct).order_by(ThroughputProduct.item_id)).all()
    assert [product.name for product in products] == ["Whole Wheat 20kg", "Bulka Maize"]
    assert products[0].default_bag_size == 20
    assert products[0].is_bulka_default is False
    assert products[0].active is True
    assert products[0].client_name == "Hunter"
    assert products[1].default_bag_size is None
    assert products[1].is_bulka_default is True
    # Every costing SKU is selectable in the throughput picker, even hidden ones.
    assert products[1].active is True
    assert products[1].client_name == "Hunter"


def test_seed_throughput_products_from_costing_updates_existing_by_item_id():
    db = _session()
    mix = _costing_mix(db)
    db.add(
        Product(
            tenant_id="hunter-premium-produce",
            client_name="Hunter",
            item_id="1001",
            name="Updated Wheat 25kg",
            mix_id=mix.id,
            sale_type="standard",
            unit_of_measure="25kg bag",
            visible=True,
        )
    )
    db.add(
        ThroughputProduct(
            tenant_id="hunter-premium-produce",
            item_id="1001",
            name="Old Wheat",
            default_bag_size=20,
            active=False,
            notes="Seeded from costing products",
        )
    )
    db.flush()

    report = seed_throughput_products_from_costing(db)

    assert report == {"created": 0, "updated": 1, "skipped": 0}
    products = db.scalars(select(ThroughputProduct)).all()
    assert len(products) == 1
    assert products[0].name == "Updated Wheat 25kg"
    assert products[0].default_bag_size == 25
    assert products[0].active is True


def test_upload_import_keeps_blank_destination_flags_false():
    db = _session()
    csv_bytes = (
        "Date,Product,Quantity,Type,Bag Size,For Order,For Stock,Job Number\n"
        "2026-06-12,Specialty Pigeon Breeder,40,bags,20,,,\n"
    ).encode("utf-8")

    result = import_entries_from_file(
        db,
        filename="throughput-import.csv",
        content=csv_bytes,
        tenant_id="test-tenant",
        created_by="tester@example.com",
    )

    assert result["entries_imported"] == 1
    entry = db.scalar(select(ProductionThroughput))
    assert entry is not None
    assert entry.for_order is False
    assert entry.for_stock is False
    assert entry.job_number is None


def test_upload_import_detects_month_first_dates_consistently():
    # The pasted sheet is US month-first (M/D/Y). "9/23/2025" is unambiguous, so
    # the ambiguous "12/9/2025" must follow the same convention: 9 December, not
    # 12 September (which the old day-first-by-default parser produced).
    db = _session()
    csv_bytes = (
        "Date,Product,Item ID,Quantity,Type,Bag Size,Packed By\n"
        "12/9/2025,Whole Wheat Cleaned & Graded 20kg,373022,156,bags,20,Jake\n"
        "9/23/2025,Steam Rolled Barley 20kg,568240,34,bags,20,jake\n"
        "9/24/2025,Stock Mix 20kg,540725,153,bags,20,jake\n"
    ).encode("utf-8")

    result = import_entries_from_file(
        db,
        filename="throughput-import.csv",
        content=csv_bytes,
        tenant_id="test-tenant",
        created_by="tester@example.com",
    )

    assert result["entries_imported"] == 3
    dates = {
        e.product_name_snapshot: e.production_date
        for e in db.scalars(select(ProductionThroughput)).all()
    }
    assert dates["Whole Wheat Cleaned & Graded 20kg"] == date(2025, 12, 9)
    assert dates["Steam Rolled Barley 20kg"] == date(2025, 9, 23)
    assert dates["Stock Mix 20kg"] == date(2025, 9, 24)


def test_upload_import_keeps_day_first_dates_for_australian_sheets():
    # A genuinely day-first file (23/9/2025 proves D/M/Y) must stay day-first, so
    # 12/9/2025 reads as 12 September.
    db = _session()
    csv_bytes = (
        "Date,Product,Quantity,Type,Bag Size\n"
        "23/9/2025,Stock Mix 20kg,10,bags,20\n"
        "12/9/2025,Stock Mix 20kg,10,bags,20\n"
    ).encode("utf-8")

    import_entries_from_file(
        db,
        filename="throughput-import.csv",
        content=csv_bytes,
        tenant_id="test-tenant",
        created_by="tester@example.com",
    )

    produced = sorted(
        e.production_date for e in db.scalars(select(ProductionThroughput)).all()
    )
    assert produced == [date(2025, 9, 12), date(2025, 9, 23)]


def test_upload_import_does_not_treat_unknown_destination_text_as_true():
    db = _session()
    csv_bytes = (
        "Date,Product,Quantity,Type,Bag Size,For Order,For Stock\n"
        "2026-06-12,Specialty Pigeon Breeder,40,bags,20,stock,\n"
    ).encode("utf-8")

    result = import_entries_from_file(
        db,
        filename="throughput-import.csv",
        content=csv_bytes,
        tenant_id="test-tenant",
        created_by="tester@example.com",
    )

    assert result["entries_imported"] == 1
    entry = db.scalar(select(ProductionThroughput))
    assert entry is not None
    assert entry.for_order is False
    assert entry.for_stock is False
