import pytest
from django.contrib.auth import get_user_model
from django.test.utils import CaptureQueriesContext
from django.db import connection
from datetime import date
from rest_framework.test import APIClient
from apps.asset_management.models import (
    Asset, AssetCategory, AssetLocation, AssetAssignment, Maintenance, Procurement, Vendor,
    StockItem, StockMovement, SellableItem,
)


@pytest.fixture
def organization(db):
    from apps.companies.models import Organization
    return Organization.objects.create(name="Test Org")


@pytest.fixture
def user(db, organization):
    from apps.companies.models import OrganizationMembership
    from apps.hr.signals import ensure_employee_stub
    User = get_user_model()
    u = User.objects.create_user(email="asset@example.com", password="pass123")
    OrganizationMembership.objects.create(organization=organization, user=u)
    ensure_employee_stub(u)
    return u


@pytest.fixture
def client(db, organization):
    from apps.companies.models import OrganizationMembership
    User = get_user_model()
    admin = User.objects.create_superuser(email="asset-admin@test.local", password="x")
    auto_org = admin.organizations.first()
    OrganizationMembership.objects.filter(organization=auto_org, user=admin).update(organization=organization)
    auto_org.delete()
    c = APIClient()
    c.force_authenticate(admin)
    return c


@pytest.fixture
def category(organization):
    return AssetCategory.objects.create(organization=organization, name="Electronics")


@pytest.fixture
def location(organization):
    return AssetLocation.objects.create(organization=organization, name="Office A")


@pytest.fixture
def vendor(organization):
    return Vendor.objects.create(organization=organization, name="Tech Supplier Inc")


@pytest.mark.django_db
class TestVendorModel:
    def test_create_vendor(self, organization):
        vendor = Vendor.objects.create(
            organization=organization,
            name="Office Supplies Co",
            email="contact@officesupplies.com"
        )
        assert vendor.name == "Office Supplies Co"


@pytest.mark.django_db
class TestAssetCategoryModel:
    def test_create_category(self, organization):
        cat = AssetCategory.objects.create(organization=organization, name="Furniture")
        assert cat.slug == "furniture"


@pytest.mark.django_db
class TestAssetLocationModel:
    def test_create_location(self, organization):
        loc = AssetLocation.objects.create(organization=organization, name="Warehouse B")
        assert loc.slug == "warehouse-b"


@pytest.mark.django_db
class TestAssetModel:
    def test_create_asset(self, organization, category, location):
        asset = Asset.objects.create(
            organization=organization,
            name="MacBook Pro",
            asset_tag="LAPTOP-001",
            category=category,
            location=location
        )
        assert asset.name == "MacBook Pro"
        assert asset.status == "available"

    def test_asset_unique_tag(self, organization, category, location):
        Asset.objects.create(organization=organization, name="Asset 1", asset_tag="TAG-001", category=category)
        with pytest.raises(Exception):
            Asset.objects.create(organization=organization, name="Asset 2", asset_tag="TAG-001", category=category)


@pytest.mark.django_db
class TestAssetAssignmentModel:
    def test_create_assignment(self, organization, category, location, user):
        from apps.hr.models import Employee
        # The hr post_save signal auto-creates an Employee per user — reuse it.
        employee = Employee.objects.get(user=user)
        asset = Asset.objects.create(
            organization=organization,
            name="Monitor",
            asset_tag="MON-001",
            category=category,
            location=location
        )
        assignment = AssetAssignment.objects.create(
            asset=asset,
            employee=employee,
            assigned_date=date.today()
        )
        assert assignment.status == "assigned"


@pytest.mark.django_db
class TestMaintenanceModel:
    def test_create_maintenance(self, organization, category, location):
        asset = Asset.objects.create(organization=organization, name="Printer", asset_tag="PRINT-001", category=category)
        maint = Maintenance.objects.create(
            asset=asset,
            title="Monthly check",
            description="Regular maintenance",
            scheduled_date=date.today()
        )
        assert maint.status == "scheduled"


@pytest.mark.django_db
class TestProcurementModel:
    def test_create_procurement(self, organization, user, vendor):
        proc = Procurement.objects.create(
            organization=organization,
            title="Order 50 laptops",
            requested_by=user,
            vendor=vendor
        )
        assert proc.status == "draft"


@pytest.mark.django_db
class TestProcurementAssetLink:
    def test_asset_fk_round_trips(self, organization, user, category, location):
        asset = Asset.objects.create(organization=organization, name="Laptop", asset_tag="LT-1", category=category, location=location)
        proc = Procurement.objects.create(organization=organization, title="Replace laptop", requested_by=user, asset=asset)
        proc.refresh_from_db()
        assert str(proc.asset_id).replace("-", "") == str(asset.id).replace("-", "")

    def test_viewset_filters_by_asset(self, organization, client, user, category, location):
        asset = Asset.objects.create(organization=organization, name="Laptop", asset_tag="LT-2", category=category, location=location)
        other_asset = Asset.objects.create(organization=organization, name="Monitor", asset_tag="MON-1")
        Procurement.objects.create(organization=organization, title="For laptop", requested_by=user, asset=asset)
        Procurement.objects.create(organization=organization, title="For monitor", requested_by=user, asset=other_asset)

        resp = client.get("/api/procurements/", {"asset": str(asset.id)})
        body = resp.json()
        results = body["results"] if isinstance(body, dict) and "results" in body else body
        assert len(results) == 1
        assert results[0]["title"] == "For laptop"
        assert results[0]["asset"].replace("-", "") == str(asset.id).replace("-", "")


@pytest.mark.django_db
class TestStockMovementWeightedAverageCost:
    def test_single_stock_in_sets_cost(self, organization, location):
        item = StockItem.objects.create(organization=organization, name="Tote Bag", sku="ACC-001")
        movement = StockMovement.objects.create(
            item=item, location=location, movement_type="in", quantity=100, unit_cost=10_000,
        )
        movement.apply()
        item.refresh_from_db()
        assert item.unit_cost == 10_000

    def test_second_stock_in_averages_cost(self, organization, location):
        item = StockItem.objects.create(organization=organization, name="Tote Bag", sku="ACC-002")
        first = StockMovement.objects.create(
            item=item, location=location, movement_type="in", quantity=100, unit_cost=10_000,
        )
        first.apply()
        second = StockMovement.objects.create(
            item=item, location=location, movement_type="in", quantity=100, unit_cost=20_000,
        )
        second.apply()
        item.refresh_from_db()
        # (100*10000 + 100*20000) / 200 = 15000
        assert item.unit_cost == 15_000

    def test_stock_out_does_not_change_cost(self, organization, location):
        item = StockItem.objects.create(organization=organization, name="Tote Bag", sku="ACC-003")
        StockMovement.objects.create(
            item=item, location=location, movement_type="in", quantity=100, unit_cost=10_000,
        ).apply()
        StockMovement.objects.create(
            item=item, location=location, movement_type="out", quantity=-30,
        ).apply()
        item.refresh_from_db()
        assert item.unit_cost == 10_000
        assert item.total_quantity == 70


@pytest.mark.django_db
class TestSellableItem:
    def test_create_book(self, organization, location):
        item = StockItem.objects.create(organization=organization, name="Annual Report 2026", sku="BOOK-010")
        sellable = SellableItem.objects.create(stock_item=item, category="book", sale_price=50_000)
        assert sellable.category == "book"
        assert sellable.publication is None

    def test_create_accessory(self, organization, location):
        item = StockItem.objects.create(organization=organization, name="Tote Bag", sku="ACC-010")
        sellable = SellableItem.objects.create(stock_item=item, category="accessory", sale_price=75_000)
        assert sellable.category == "accessory"
        assert sellable.publication is None


@pytest.mark.django_db
class TestStockItemTotalQuantityNoN1:
    def test_total_quantity_uses_prefetch_cache(self, organization, location):
        from apps.asset_management.models import StockLevel

        for i in range(5):
            item = StockItem.objects.create(organization=organization, name=f"Item {i}", sku=f"SKU-{i}")
            StockLevel.objects.create(item=item, location=location, quantity=10)

        qs = StockItem.objects.prefetch_related("levels")
        with CaptureQueriesContext(connection) as ctx:
            items = list(qs)
            totals = [it.total_quantity for it in items]
        # 1 query for StockItem + 1 for the levels prefetch, regardless of row count.
        assert len(ctx.captured_queries) == 2
        assert totals == [10, 10, 10, 10, 10]


@pytest.mark.django_db
class TestAssetViewSetFilters:
    def test_filters_by_status_category_location(self, organization, client, category, location):
        Asset.objects.create(organization=organization, name="Laptop A", asset_tag="A-1", status="available", category=category, location=location)
        Asset.objects.create(organization=organization, name="Laptop B", asset_tag="A-2", status="assigned")

        resp = client.get("/api/assets/", {"status": "available"})
        body = resp.json()
        results = body["results"] if isinstance(body, dict) and "results" in body else body
        assert len(results) == 1
        assert results[0]["asset_tag"] == "A-1"

        resp = client.get("/api/assets/", {"category": str(category.id)})
        body = resp.json()
        results = body["results"] if isinstance(body, dict) and "results" in body else body
        assert len(results) == 1
        assert results[0]["asset_tag"] == "A-1"


@pytest.mark.django_db
class TestPaginationEnvelope:
    def test_assets_list_is_paginated(self, organization, client):
        for i in range(3):
            Asset.objects.create(organization=organization, name=f"Asset {i}", asset_tag=f"TAG-{i}")
        resp = client.get("/api/assets/")
        body = resp.json()
        assert "results" in body and "count" in body
        assert body["count"] == 3

    def test_asset_categories_not_paginated(self, client, category):
        resp = client.get("/api/asset-categories/")
        body = resp.json()
        assert isinstance(body, list)
