from datetime import timedelta
from decimal import Decimal

from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.test import TestCase
from django.utils import timezone
from rest_framework.test import APIClient

from shop_management.models.data.inventory import InventoryBatch, StockBalance, StockMovement
from shop_management.models.data.organization import Warehouse
from shop_management.models.data.product import Product
from shop_management.models.data.shop import Shop
from shop_management.models.data.stock_adjustment import StockAdjustment, StockAdjustmentItem
from shop_management.models.data.utils import Currency
from shop_management.services.inventory import receive_stock
from shop_management.services.stock_adjustments import post_stock_adjustment, void_stock_adjustment


class StockAdjustmentTests(TestCase):
    def setUp(self):
        self.user = get_user_model().objects.create_user(
            username="adjuster", password="test", is_staff=True
        )
        self.user.allowed_tabs = ["stock"]
        self.user.save(update_fields=["allowed_tabs"])
        self.shop = Shop.objects.create(owner=self.user, name="Kabul")
        self.warehouse = Warehouse.objects.create(shop=self.shop, code="MAIN", name="Main")
        self.currency = Currency.objects.create(shop=self.shop, title="AFN")
        self.product = Product.objects.create(shop=self.shop, name="Medicine", requires_batch=True)
        self.batch = InventoryBatch.objects.create(
            product=self.product, batch_number="B-1",
            expiry_date=timezone.localdate() + timedelta(days=90),
        )

    def adjustment(self, direction, quantity, unit_cost=None):
        adjustment = StockAdjustment.objects.create(
            warehouse=self.warehouse, reason="Physical count", created_by=self.user
        )
        StockAdjustmentItem.objects.create(
            adjustment=adjustment, product=self.product, batch=self.batch,
            direction=direction, quantity=quantity, unit_cost=unit_cost,
            currency=self.currency, created_by=self.user,
        )
        return adjustment

    def test_incoming_adjustment_posts_and_voids_with_reversal(self):
        adjustment = self.adjustment("IN", "5", "12")
        post_stock_adjustment(adjustment.id, user=self.user)
        adjustment.refresh_from_db()
        balance = StockBalance.objects.get(batch=self.batch)
        self.assertEqual(adjustment.status, "POSTED")
        self.assertRegex(adjustment.adjustment_number, r"^ADJ-\d{4}-\d{6}$")
        self.assertEqual(balance.quantity, Decimal("5.0000"))
        self.assertEqual(StockMovement.objects.filter(movement_type="ADJUSTMENT_IN").count(), 1)

        void_stock_adjustment(adjustment.id, user=self.user, reason="Counting error")
        adjustment.refresh_from_db()
        balance.refresh_from_db()
        self.assertEqual(adjustment.status, "VOID")
        self.assertEqual(balance.quantity, Decimal("0.0000"))
        self.assertEqual(StockMovement.objects.filter(movement_type="VOID_REVERSAL").count(), 1)

    def test_outgoing_adjustment_prevents_negative_stock(self):
        adjustment = self.adjustment("OUT", "2")
        with self.assertRaises(ValidationError):
            post_stock_adjustment(adjustment.id, user=self.user)
        adjustment.refresh_from_db()
        self.assertEqual(adjustment.status, "DRAFT")
        self.assertEqual(StockMovement.objects.count(), 0)

    def test_outgoing_adjustment_uses_batch_average_cost(self):
        receive_stock(
            warehouse=self.warehouse, product=self.product, batch=self.batch,
            quantity="10", unit_cost="15", currency=self.currency,
            movement_type=StockMovement.MovementType.OPENING,
        )
        adjustment = self.adjustment("OUT", "3")
        post_stock_adjustment(adjustment.id, user=self.user)
        movement = StockMovement.objects.get(movement_type="ADJUSTMENT_OUT")
        self.assertEqual(movement.unit_cost, Decimal("15.000000"))
        self.assertEqual(StockBalance.objects.get(batch=self.batch).quantity, Decimal("7.0000"))

    def test_api_creates_nested_draft_and_posts_it(self):
        client = APIClient()
        client.force_authenticate(self.user)
        response = client.post(
            "/api/shop/stock-adjustments/",
            {
                "warehouse": self.warehouse.id,
                "adjustment_date": str(timezone.localdate()),
                "reason": "Opening count",
                "items": [{
                    "product": self.product.id, "batch": self.batch.id,
                    "direction": "IN", "quantity": "4.0000",
                    "unit_cost": "20.000000", "currency": self.currency.id,
                    "remarks": "Counted",
                }],
            }, format="json",
        )
        self.assertEqual(response.status_code, 201, response.data)
        adjustment_id = response.data["id"]
        posted = client.post(f"/api/shop/stock-adjustments/{adjustment_id}/post/")
        self.assertEqual(posted.status_code, 200, posted.data)
        edit = client.patch(
            f"/api/shop/stock-adjustments/{adjustment_id}/",
            {"reason": "Changed"}, format="json",
        )
        self.assertEqual(edit.status_code, 400)
