from datetime import date, timedelta
from decimal import Decimal
from uuid import uuid4
import pytest

from app_new.extensions import db
from app_new.core.models import (
    ProductGeneric, CommercialProduct, ProductPackaging, StockLocation,
    Staff, ReusableContainer, CleaningForecastConfig, CollegeClosure, ClosureWorkDay,
)
from app_new.cleaning.services.stock import (
    StockError, receive_stock, issue_stock, transfer_stock, dilute,
)
from app_new.cleaning.services.forecast import forecast_product


def setup_stock():
    product = ProductGeneric(name=f"Nettoyant test {uuid4().hex[:8]}", reference_unit="L", product_type="liquide",
                             forecast_daily_quantity=Decimal("1.2"), stock_security=Decimal("2"))
    ref = CommercialProduct(generic_product=product, commercial_name="Référence test")
    packaging = ProductPackaging(generic_product=product, commercial_product=ref, name="Bidon 5 L",
                                 purchase_unit="bidon", units_per_package=1,
                                 reference_quantity_per_package=Decimal("5"))
    suffix = uuid4().hex[:8]
    loc_a = StockLocation(name=f"Stock test A {suffix}")
    loc_b = StockLocation(name=f"Stock test B {suffix}")
    db.session.add_all([product, ref, packaging, loc_a, loc_b])
    db.session.flush()
    return product, ref, packaging, loc_a, loc_b


def test_reception_conversion_and_fefo(app):
    with app.app_context():
        product, ref, packaging, loc_a, loc_b = setup_stock()
        receive_stock(generic_product_id=product.id, commercial_product_id=ref.id, packaging_id=packaging.id,
                      packages=2, lot_number="ANCIEN", received_at=date.today(),
                      expiry_date=date.today() + timedelta(days=3), location_id=loc_a.id)
        receive_stock(generic_product_id=product.id, commercial_product_id=ref.id, packaging_id=packaging.id,
                      packages=2, lot_number="RECENT", received_at=date.today(),
                      expiry_date=date.today() + timedelta(days=90), location_id=loc_a.id)
        db.session.commit()
        issue_stock(generic_product_id=product.id, quantity=Decimal("6"), source_location_id=loc_a.id)
        db.session.commit()
        lots = {lot.lot_number: sum((b.quantity for b in lot.balances), Decimal("0"))
                for lot in product.lots}
        assert lots["ANCIEN"] == Decimal("4.000000")
        assert lots["RECENT"] == Decimal("10.000000")


def test_expired_lot_rejected_and_no_negative_stock(app):
    with app.app_context():
        product, ref, packaging, loc_a, _ = setup_stock()
        receive_stock(generic_product_id=product.id, commercial_product_id=ref.id, packaging_id=packaging.id,
                      packages=1, lot_number="EXPIRE", received_at=date.today(),
                      expiry_date=date.today() - timedelta(days=1), location_id=loc_a.id)
        db.session.commit()
        with pytest.raises(StockError):
            issue_stock(generic_product_id=product.id, quantity=1, source_location_id=loc_a.id)
        with pytest.raises(StockError):
            issue_stock(generic_product_id=product.id, quantity=100, source_location_id=loc_a.id)
        db.session.rollback()


def test_transfer_and_dilution(app):
    with app.app_context():
        product, ref, packaging, loc_a, loc_b = setup_stock()
        receive_stock(generic_product_id=product.id, commercial_product_id=ref.id, packaging_id=packaging.id,
                      packages=1, lot_number="TRANS", received_at=date.today(), location_id=loc_a.id)
        container = ReusableContainer(identifier=f"FLACON-TEST-{uuid4().hex[:8]}", container_type="750 ml", volume_liters=Decimal("0.750"))
        db.session.add(container); db.session.flush()
        transfer_stock(generic_product_id=product.id, quantity=Decimal("1"), source_location_id=loc_a.id, destination_location_id=loc_b.id)
        db.session.commit()
        assert sum((b.quantity for b in product.lots[0].balances if b.location_id == loc_b.id), Decimal("0")) == Decimal("1.000000")
        dilute(generic_product_id=product.id, total_liters=Decimal("0.750"), ratio_percent=Decimal("2"),
               source_location_id=loc_b.id, container_id=container.id)
        db.session.commit()
        assert container.current_product_id == product.id
        assert sum((b.quantity for b in product.lots[0].balances if b.location_id == loc_b.id), Decimal("0")) == Decimal("0.985000")


def test_forecast_distinguishes_school_days_permanence_and_closure(app):
    with app.app_context():
        product, ref, packaging, loc_a, _ = setup_stock()
        start = date(2026, 9, 7)
        closure = CollegeClosure(name="Vacances test", start_date=start + timedelta(days=5),
                                 end_date=start + timedelta(days=9), closure_type="vacances_scolaires")
        db.session.add(closure); db.session.flush()
        db.session.add(ClosureWorkDay(closure_id=closure.id, work_date=start + timedelta(days=6),
                                      start_time=__import__("datetime").time(8), end_time=__import__("datetime").time(12)))
        db.session.commit()
        result = forecast_product(product, start + timedelta(days=9), start_date=start)
        assert result["school_days_consumption"] > 0
        assert result["permanence_consumption"] == Decimal("0.480000")
        assert result["closed_consumption"] == 0
