"""
Inventory Module — Pytest Fixtures.
فیکسچرهای مشترک برای تست‌های ماژول انبار و موجودی.
"""
import sys
from pathlib import Path
import uuid
from datetime import date, timedelta
from decimal import Decimal

PROJECT_ROOT = str(Path(__file__).resolve().parent.parent.parent.parent)
if PROJECT_ROOT not in sys.path:
    sys.path.insert(0, PROJECT_ROOT)

import pytest

from apps.core.tenant.models import Tenant, Domain
from apps.core.tenant.middleware import _thread_locals
from apps.core.auth.models import User

# Force-load DRF router registrations
import modules.inventory.backend.api.v1.views  # noqa: F401

from modules.inventory.backend.infrastructure.persistence.models import (
    ItemTypeModel,
    ItemGroupModel,
    UnitOfMeasureModel,
    ItemModel,
    ItemAttributeModel,
    ItemUOMModel,
    WarehouseModel,
    StorageLocationModel,
    ItemWarehouseModel,
)
from modules.inventory.backend.infrastructure.persistence.models_extended import (
    CostingMethodModel,
    StockStatusModel,
    BatchModel,
    SerialNumberModel,
    StockLedgerEntryModel,
    StockBalanceModel,
    ItemCostingModel,
    CostLayerModel,
    FinancialPostingModel,
    StockReservationModel,
    StockAllocationModel,
    PhysicalInventoryModel,
    PhysicalInventoryLineModel,
    CycleCountScheduleModel,
    InspectionLotModel,
    InspectionResultModel,
    QualityActionModel,
    StockMovementSummaryModel,
    StockAgingBucketModel,
)


# ═══════════════════════════════════════════════════════════════════
# TENANT & AUTH
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture(autouse=True)
def _patch_tenant_middleware(tenant, monkeypatch):
    """Patch tenant middleware so every request sees the test tenant."""
    from django.db import connection
    from apps.core.tenant import middleware as tenant_mw

    monkeypatch.setattr(tenant_mw, "get_current_tenant", lambda: tenant)
    if hasattr(connection, "set_tenant"):
        connection.set_tenant(tenant)
    yield
    if hasattr(_thread_locals, "tenant"):
        del _thread_locals.tenant
    if hasattr(_thread_locals, "request"):
        del _thread_locals.request


@pytest.fixture(scope="function")
def tenant(db):
    t = Tenant.objects.create(
        name="Test Tenant", slug="test-tenant", schema_name="public"
    )
    Domain.objects.create(domain="localhost", tenant=t, is_primary=True)
    return t


@pytest.fixture
def user(tenant):
    return User.objects.create_user(
        email="inv-user@example.com",
        password="testpass123",
        first_name="Inv",
        last_name="User",
        tenant=tenant,
    )


@pytest.fixture
def admin_user(tenant):
    return User.objects.create_superuser(
        email="inv-admin@example.com",
        password="adminpass123",
        first_name="Inv",
        last_name="Admin",
        tenant=tenant,
    )


@pytest.fixture
def api_client():
    from rest_framework.test import APIClient

    class TenantAPIClient(APIClient):
        def request(self, **kwargs):
            if "HTTP_HOST" not in kwargs:
                kwargs["HTTP_HOST"] = "localhost"
            return super().request(**kwargs)

    return TenantAPIClient()


@pytest.fixture
def authenticated_client(api_client, user):
    from rest_framework_simplejwt.tokens import RefreshToken

    refresh = RefreshToken.for_user(user)
    api_client.credentials(HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}")
    return api_client


@pytest.fixture
def admin_client(api_client, admin_user):
    from rest_framework_simplejwt.tokens import RefreshToken

    refresh = RefreshToken.for_user(admin_user)
    api_client.credentials(HTTP_AUTHORIZATION=f"Bearer {refresh.access_token}")
    return api_client


# ═══════════════════════════════════════════════════════════════════
# MASTER DATA
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def costing_method(tenant):
    return CostingMethodModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        method_code="FIFO",
        method_name="اولین ورود اولین خروج",
        description="FIFO Costing",
        is_active=True,
    )


@pytest.fixture
def item_type(tenant):
    return ItemTypeModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        type_code="RAW",
        type_name="ماده اولیه",
        description="مواد اولیه تولیدی",
        is_active=True,
    )


@pytest.fixture
def item_group(tenant, costing_method):
    return ItemGroupModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        group_code="MTL",
        group_name="مواد",
        default_costing_method=costing_method,
        level=0,
        is_active=True,
    )


@pytest.fixture
def item_group_child(tenant, item_group):
    return ItemGroupModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        group_code="MTL-STL",
        group_name="فولاد",
        parent_group=item_group,
        level=1,
        is_active=True,
    )


@pytest.fixture
def uom(tenant):
    return UnitOfMeasureModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        uom_code="KG",
        uom_name="Kilogram",
        uom_name_local="کیلوگرم",
        uom_type=UnitOfMeasureModel.UOMType.WEIGHT,
        decimal_places=2,
        is_active=True,
    )


@pytest.fixture
def uom_piece(tenant):
    return UnitOfMeasureModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        uom_code="PCS",
        uom_name="Piece",
        uom_name_local="عدد",
        uom_type=UnitOfMeasureModel.UOMType.QUANTITY,
        decimal_places=0,
        is_active=True,
    )


@pytest.fixture
def item(tenant, item_type, item_group, uom):
    return ItemModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item_code="MAT-001",
        item_name="Steel Rod",
        item_name_local="میلگرد",
        item_type=item_type,
        item_group=item_group,
        base_uom=uom,
        is_batch_managed=True,
        is_serial_managed=False,
        item_status=ItemModel.ItemStatus.ACTIVE,
        is_purchasable=True,
        is_saleable=True,
        is_stockable=True,
        lead_time_days=7,
    )


@pytest.fixture
def item_serial_managed(tenant, item_type, item_group, uom_piece):
    return ItemModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item_code="EQP-001",
        item_name="Laptop",
        item_name_local="لپ‌تاپ",
        item_type=item_type,
        item_group=item_group,
        base_uom=uom_piece,
        is_batch_managed=False,
        is_serial_managed=True,
        item_status=ItemModel.ItemStatus.ACTIVE,
        is_purchasable=True,
        is_saleable=True,
        is_stockable=True,
    )


@pytest.fixture
def item_attribute(tenant, item):
    return ItemAttributeModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        attribute_name="قطر",
        attribute_value="12mm",
        attribute_data_type="Text",
        display_order=1,
    )


@pytest.fixture
def item_uom(tenant, item, uom):
    return ItemUOMModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        uom=uom,
        conversion_factor=Decimal("1.0"),
        is_base_uom=True,
        is_stock_uom=True,
        is_active=True,
    )


# ═══════════════════════════════════════════════════════════════════
# WAREHOUSE
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def warehouse(tenant):
    return WarehouseModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        warehouse_code="WH-01",
        warehouse_name="انبار مرکزی",
        warehouse_type=WarehouseModel.WarehouseType.PHYSICAL,
        city="تهران",
        province="تهران",
        country="IR",
        is_negative_stock_allowed=False,
        is_active=True,
    )


@pytest.fixture
def warehouse_transit(tenant):
    return WarehouseModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        warehouse_code="WH-TR",
        warehouse_name="انبار ترانزیت",
        warehouse_type=WarehouseModel.WarehouseType.TRANSIT,
        is_active=True,
    )


@pytest.fixture
def storage_location(tenant, warehouse):
    return StorageLocationModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        location_code="A-01-01",
        location_name="ناحیه A قفسه 1",
        warehouse=warehouse,
        location_type=StorageLocationModel.LocationType.RACK,
        level=2,
        is_picking_location=True,
        is_active=True,
    )


@pytest.fixture
def item_warehouse(tenant, item, warehouse, storage_location):
    return ItemWarehouseModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        warehouse=warehouse,
        min_stock_level=Decimal("100.0000"),
        max_stock_level=Decimal("10000.0000"),
        reorder_point=Decimal("500.0000"),
        safety_stock=Decimal("200.0000"),
        reorder_quantity=Decimal("1000.0000"),
        default_storage_location=storage_location,
        is_active=True,
    )


# ═══════════════════════════════════════════════════════════════════
# STOCK STATUS
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def stock_status(tenant):
    return StockStatusModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        status_code="AVAIL",
        status_name="در دسترس",
        status_type=StockStatusModel.StatusType.AVAILABLE,
        is_available_for_sales=True,
        is_available_for_production=True,
        sort_order=1,
        is_active=True,
    )


@pytest.fixture
def stock_status_blocked(tenant):
    return StockStatusModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        status_code="BLK",
        status_name="مسدود",
        status_type=StockStatusModel.StatusType.BLOCKED,
        is_available_for_sales=False,
        is_blocked_for_movement=True,
        sort_order=2,
        is_active=True,
    )


# ═══════════════════════════════════════════════════════════════════
# BATCH & SERIAL
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def batch(tenant, item):
    return BatchModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        batch_number="B-2024-001",
        item=item,
        manufacturing_date=date(2024, 1, 1),
        expiry_date=date(2025, 1, 1),
        batch_status=BatchModel.BatchStatus.ACTIVE,
        quality_status=BatchModel.QualityStatus.APPROVED,
        is_active=True,
    )


@pytest.fixture
def serial_number(tenant, item_serial_managed, warehouse, storage_location):
    return SerialNumberModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        serial_no="SN-2024-0001",
        item=item_serial_managed,
        current_warehouse=warehouse,
        current_location=storage_location,
        serial_status=SerialNumberModel.SerialStatus.IN_STOCK,
        quality_status=SerialNumberModel.QualityStatus.APPROVED,
        is_active=True,
    )


# ═══════════════════════════════════════════════════════════════════
# STOCK LEDGER & BALANCE
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def stock_ledger_entry(tenant, item, warehouse, storage_location, uom, batch, stock_status):
    return StockLedgerEntryModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        entry_number="SLE-2024-001",
        posting_date=date(2024, 1, 15),
        item=item,
        warehouse=warehouse,
        location=storage_location,
        batch=batch,
        stock_status=stock_status,
        transaction_type=StockLedgerEntryModel.TransactionType.GR,
        quantity=Decimal("1000.0000"),
        uom=uom,
        quantity_in_base_uom=Decimal("1000.0000"),
        unit_cost=Decimal("50000.0000"),
        total_cost=Decimal("50000000.0000"),
        currency_code="IRR",
    )


@pytest.fixture
def stock_balance(tenant, item, warehouse, uom, stock_status):
    return StockBalanceModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        warehouse=warehouse,
        stock_status=stock_status,
        quantity_on_hand=Decimal("1000.0000"),
        quantity_reserved=Decimal("100.0000"),
        quantity_allocated=Decimal("50.0000"),
        quantity_available=Decimal("850.0000"),
        quantity_in_transit=Decimal("0.0000"),
        uom=uom,
        average_unit_cost=Decimal("50000.0000"),
        total_value=Decimal("50000000.0000"),
    )


# ═══════════════════════════════════════════════════════════════════
# COSTING
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def item_costing(tenant, item, costing_method):
    return ItemCostingModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        costing_method=costing_method,
        standard_cost=Decimal("50000.0000"),
        moving_average_cost=Decimal("48000.0000"),
        last_purchase_cost=Decimal("52000.0000"),
        currency_code="IRR",
        last_cost_update_date=date(2024, 1, 15),
        is_active=True,
    )


@pytest.fixture
def cost_layer(tenant, item, warehouse, uom, stock_ledger_entry):
    return CostLayerModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        warehouse=warehouse,
        receipt_date=date(2024, 1, 15),
        receipt_sle=stock_ledger_entry,
        unit_cost=Decimal("50000.0000"),
        original_quantity=Decimal("1000.0000"),
        remaining_quantity=Decimal("800.0000"),
        uom=uom,
        currency_code="IRR",
        is_fully_consumed=False,
    )


@pytest.fixture
def financial_posting(tenant, stock_ledger_entry):
    return FinancialPostingModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        stock_ledger=stock_ledger_entry,
        posting_date=date(2024, 1, 15),
        debit_account="1301",
        credit_account="2101",
        amount=Decimal("50000000.0000"),
        currency_code="IRR",
        posting_status=FinancialPostingModel.PostingStatus.PENDING,
    )


# ═══════════════════════════════════════════════════════════════════
# RESERVATION & ALLOCATION
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def reservation(tenant, item, warehouse, uom):
    return StockReservationModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        reservation_number="RSV-2024-001",
        reservation_date=date(2024, 2, 1),
        item=item,
        warehouse=warehouse,
        reserved_quantity=Decimal("100.0000"),
        allocated_quantity=Decimal("0.0000"),
        uom=uom,
        source_document_type="SALES_ORDER",
        source_document_id=uuid.uuid4(),
        priority_level=10,
        reservation_status=StockReservationModel.ReservationStatus.ACTIVE,
    )


@pytest.fixture
def allocation(tenant, item, warehouse, uom, reservation):
    return StockAllocationModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        allocation_number="ALC-2024-001",
        allocation_date=date(2024, 2, 5),
        reservation=reservation,
        item=item,
        warehouse=warehouse,
        allocated_quantity=Decimal("50.0000"),
        picked_quantity=Decimal("0.0000"),
        uom=uom,
        allocation_status=StockAllocationModel.AllocationStatus.ALLOCATED,
    )


# ═══════════════════════════════════════════════════════════════════
# PHYSICAL INVENTORY
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def physical_inventory(tenant, warehouse):
    return PhysicalInventoryModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        inventory_number="PI-2024-001",
        inventory_date=date(2024, 3, 1),
        inventory_type=PhysicalInventoryModel.InventoryType.FULL,
        warehouse=warehouse,
        inventory_status=PhysicalInventoryModel.InventoryStatus.DRAFT,
        is_blind_count=False,
        is_stock_frozen=False,
        required_count_times=2,
        variance_threshold_percent=Decimal("5.00"),
    )


@pytest.fixture
def physical_inventory_line(tenant, physical_inventory, item, warehouse, uom):
    return PhysicalInventoryLineModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        physical_inventory=physical_inventory,
        line_number=1,
        item=item,
        warehouse=warehouse,
        book_quantity=Decimal("1000.0000"),
        uom=uom,
        line_status=PhysicalInventoryLineModel.LineStatus.PENDING,
    )


@pytest.fixture
def cycle_count_schedule(tenant, warehouse):
    return CycleCountScheduleModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        schedule_name="شمارش ماهانه A",
        warehouse=warehouse,
        count_frequency_days=30,
        abc_class="A",
        next_count_date=date(2024, 4, 1),
        is_active=True,
    )


# ═══════════════════════════════════════════════════════════════════
# QUALITY MANAGEMENT
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def inspection_lot(tenant, item, warehouse, uom):
    return InspectionLotModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        inspection_lot_number="QC-2024-001",
        inspection_type=InspectionLotModel.InspectionType.INCOMING,
        inspection_date=date(2024, 1, 16),
        item=item,
        warehouse=warehouse,
        inspection_quantity=Decimal("100.0000"),
        sample_size=Decimal("10.0000"),
        uom=uom,
        inspection_status=InspectionLotModel.InspectionStatus.PENDING,
        quality_decision=InspectionLotModel.QualityDecision.PENDING,
    )


@pytest.fixture
def inspection_result(tenant, inspection_lot):
    return InspectionResultModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        inspection_lot=inspection_lot,
        inspection_characteristic="قطر",
        target_value="12.00",
        actual_value="12.05",
        tolerance_lower=Decimal("11.90"),
        tolerance_upper=Decimal("12.10"),
        result_status=InspectionResultModel.ResultStatus.PASS,
        tested_date=date(2024, 1, 16),
    )


@pytest.fixture
def quality_action(tenant, inspection_lot):
    return QualityActionModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        inspection_lot=inspection_lot,
        action_type=QualityActionModel.ActionType.RELEASE,
        action_date=date(2024, 1, 17),
        action_quantity=Decimal("100.0000"),
    )


# ═══════════════════════════════════════════════════════════════════
# REPORTING
# ═══════════════════════════════════════════════════════════════════


@pytest.fixture
def stock_movement_summary(tenant, item, warehouse):
    return StockMovementSummaryModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        summary_date=date(2024, 1, 31),
        item=item,
        warehouse=warehouse,
        transaction_type="GR",
        total_in_quantity=Decimal("5000.0000"),
        total_out_quantity=Decimal("2000.0000"),
        net_quantity=Decimal("3000.0000"),
        total_in_value=Decimal("250000000.0000"),
        total_out_value=Decimal("100000000.0000"),
        transaction_count=15,
    )


@pytest.fixture
def stock_aging_bucket(tenant, item, warehouse, batch):
    return StockAgingBucketModel.objects.create(
        id=uuid.uuid4(),
        tenant=tenant,
        item=item,
        warehouse=warehouse,
        batch=batch,
        age_in_days=45,
        aging_category="31-60",
        quantity=Decimal("500.0000"),
        value=Decimal("25000000.0000"),
        snapshot_date=date(2024, 2, 15),
    )
