from django.contrib.auth import get_user_model
from django.test import TestCase
from django.utils import timezone
from unittest.mock import patch

from stock import models as stock_models
from business import models as business_models, services

from .fixtures import create_sales_fixtures


class SalesOrderServiceTestCase(TestCase):
    def setUp(self):
        (
            self.merchant,
            self.customer,
            self.warehouse_strict,
            self.warehouse_relaxed,
            self.warehouse_strict_out,
            self.product,
            self.operator,
        ) = create_sales_fixtures()
        User = get_user_model()
        self.user = User.objects.create_user(username='sales-creator', password='pass123')
        self.strict_items = [
            {'product_id': self.product.id, 'numbers': [6, 4], 'price': '30.5', 'unit': '米'}
        ]
        self.relaxed_items = [
            {'product_id': self.product.id, 'quantity': 80, 'num_of_rolls': 2, 'price': '28.0', 'unit': '米'}
        ]
        stock_record = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse_strict_out,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            created_by=self.user,
        )
        stock_detail_1 = stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            product=self.product,
            unit=self.product.unit,
            stock_change_record=stock_record,
            quantity='6',
        )
        stock_detail_2 = stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            product=self.product,
            unit=self.product.unit,
            stock_change_record=stock_record,
            quantity='4',
        )
        self.strict_out_items = [
            {
                'product_id': self.product.id,
                'consume_detail_ids': [stock_detail_1.id, stock_detail_2.id],
                'quantity': 10,
                'price': '32.0',
                'unit': '米',
            }
        ]

    def test_create_sales_order_success(self):
        with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
            sales_order = services.create_sales_order(
                merchant=self.merchant,
                customer=self.customer,
                order_date=timezone.now().date(),
                warehouse=self.warehouse_strict,
                operator=self.operator,
                items=self.strict_items,
                remarks='销售测试',
                created_by=self.user,
            )

        self.assertIsInstance(sales_order, business_models.SalesOrder)
        self.assertEqual(sales_order.status, business_models.SalesOrderStatusEnum.PENDING)
        self.assertEqual(sales_order.items.count(), 1)
        item = sales_order.items.first()
        self.assertEqual(item.quantity_of_rolls, '6,4')
        self.assertEqual(item.split_quantity_of_rolls(), [6, 4])
        mock_delay.assert_not_called()

    def test_sales_order_human_id_generation(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )

        self.assertIsNotNone(sales_order.human_id)
        self.assertTrue(len(sales_order.human_id) >= 14)
        self.assertEqual(
            sales_order.human_id,
            f"XS{sales_order.created_at.strftime('%Y%m%d')}{sales_order.id:06d}",
        )

    def test_review_sales_order_triggers_task(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
            reviewed = services.review_sales_order(
                sales_order=sales_order,
                target_status=business_models.SalesOrderStatusEnum.APPROVED,
                reviewed_by=self.user,
            )
        self.assertEqual(reviewed.status, business_models.SalesOrderStatusEnum.APPROVED)
        mock_delay.assert_called_once_with(
            sales_order_id=sales_order.id,
            warehouse_id=self.warehouse_strict.id,
            items=[{'product_id': self.product.id, 'quantities': ['6', '4']}],
            created_by_id=self.user.id,
        )
        balance = business_models.CustomerBalance.objects.get(
            merchant=self.merchant,
            customer=self.customer,
        )
        self.assertEqual(balance.balance, sales_order.get_total_amount())
        record = business_models.BalanceChangeRecord.objects.get(
            merchant=self.merchant,
            source_type=business_models.BalanceChangeSourceEnum.SALES_ORDER,
            source_id=sales_order.id,
        )
        self.assertEqual(record.target_type, business_models.BalanceChangeTargetEnum.CUSTOMER)
        self.assertEqual(record.delta, sales_order.get_total_amount())
        self.assertEqual(record.balance_after, balance.balance)
        self.assertEqual(record.direction, business_models.BalanceChangeDirectionEnum.INCREASE)

    def test_sales_order_cancel_blocked_after_stock_created(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_relaxed,
            operator=self.operator,
            items=self.relaxed_items,
            created_by=self.user,
        )
        stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.REMOVE,
            warehouse=self.warehouse_relaxed,
            source_type=stock_models.StockChangeSourceEnum.SALES,
            source_id=sales_order.id,
        )
        with self.assertRaises(ValueError):
            services.review_sales_order(
                sales_order=sales_order,
                target_status=business_models.SalesOrderStatusEnum.CANCELLED,
                reviewed_by=self.user,
            )
        self.assertFalse(
            business_models.BalanceChangeRecord.objects.filter(
                merchant=self.merchant,
                source_type=business_models.BalanceChangeSourceEnum.SALES_ORDER,
                source_id=sales_order.id,
            ).exists()
        )

    def test_sales_order_requires_consume_ids_for_strict_out(self):
        with self.assertRaises(ValueError):
            services.create_sales_order(
                merchant=self.merchant,
                customer=self.customer,
                order_date=timezone.now().date(),
                warehouse=self.warehouse_strict_out,
                operator=self.operator,
                items=[{'product_id': self.product.id, 'quantity': 10, 'price': '20', 'unit': '米'}],
                created_by=self.user,
            )

    def test_sales_order_stores_consume_ids(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict_out,
            operator=self.operator,
            items=self.strict_out_items,
            created_by=self.user,
        )
        item = sales_order.items.first()
        consume_ids = [str(value) for value in self.strict_out_items[0]['consume_detail_ids']]
        self.assertEqual(item.consume_detail_ids, ','.join(consume_ids))
        self.assertEqual(item.quantity_of_rolls, '6,4')

    def test_review_sales_order_unfreezes_consumed_stock(self):
        stock_record = stock_models.StockChangeRecord.objects.create(
            merchant=self.merchant,
            type=stock_models.StockChangeTypeEnum.ADD,
            warehouse=self.warehouse_strict_out,
            source_type=stock_models.StockChangeSourceEnum.PURCHASE,
            created_by=self.user,
        )
        detail_1 = stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            product=self.product,
            unit=self.product.unit,
            stock_change_record=stock_record,
            quantity='6',
        )
        detail_2 = stock_models.StockChangeDetail.objects.create(
            merchant=self.merchant,
            product=self.product,
            unit=self.product.unit,
            stock_change_record=stock_record,
            quantity='4',
        )
        stock_models.StockFreeze.objects.create(
            merchant=self.merchant,
            product=self.product,
            warehouse=self.warehouse_strict_out,
            stock_detail=detail_1,
            quantity=detail_1.quantity,
            unit=detail_1.unit,
            status=stock_models.StockFreezeStatusEnum.FROZEN,
            frozen_by=self.user,
            frozen_with=1,
        )
        stock_models.StockFreeze.objects.create(
            merchant=self.merchant,
            product=self.product,
            warehouse=self.warehouse_strict_out,
            stock_detail=detail_2,
            quantity=detail_2.quantity,
            unit=detail_2.unit,
            status=stock_models.StockFreezeStatusEnum.FROZEN,
            frozen_by=self.user,
            frozen_with=2,
        )

        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict_out,
            operator=self.operator,
            items=[
                {
                    'product_id': self.product.id,
                    'consume_detail_ids': [detail_1.id, detail_2.id],
                    'quantity': 10,
                    'price': '32.0',
                    'unit': '米',
                }
            ],
            created_by=self.user,
        )

        with patch('business.services.create_sales_order_stock_entries.delay') as mock_delay:
            reviewed = services.review_sales_order(
                sales_order=sales_order,
                target_status=business_models.SalesOrderStatusEnum.APPROVED,
                reviewed_by=self.user,
            )
        self.assertEqual(reviewed.status, business_models.SalesOrderStatusEnum.APPROVED)
        mock_delay.assert_called_once()

        statuses = list(
            stock_models.StockFreeze.objects.filter(
                stock_detail_id__in=[detail_1.id, detail_2.id]
            ).values_list('status', flat=True)
        )
        self.assertEqual(
            set(statuses),
            {stock_models.StockFreezeStatusEnum.COMPLETED},
        )

    def test_sales_order_outgoing_missing_consume_ids_raises(self):
        payload = [
            {
                'product_id': self.product.id,
                'quantity': 30,
                'price': '25',
                'unit': '米',
            }
        ]
        with self.assertRaises(ValueError):
            services.create_sales_order(
                merchant=self.merchant,
                customer=self.customer,
                order_date=timezone.now().date(),
                warehouse=self.warehouse_strict_out,
                operator=self.operator,
                items=payload,
                created_by=self.user,
            )

    def test_update_sales_order_success(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        updated = services.update_sales_order(
            sales_order=sales_order,
            warehouse=self.warehouse_relaxed,
            items=self.relaxed_items,
            remarks='updated',
        )
        self.assertEqual(updated.remarks, 'updated')
        self.assertEqual(updated.items.count(), 1)
        item = updated.items.first()
        self.assertEqual(item.split_quantity_of_rolls(), [])

    def test_update_sales_order_rejects_non_pending(self):
        sales_order = services.create_sales_order(
            merchant=self.merchant,
            customer=self.customer,
            order_date=timezone.now().date(),
            warehouse=self.warehouse_strict,
            operator=self.operator,
            items=self.strict_items,
            created_by=self.user,
        )
        sales_order.status = business_models.SalesOrderStatusEnum.APPROVED
        sales_order.save(update_fields=['status'])
        with self.assertRaises(ValueError):
            services.update_sales_order(
                sales_order=sales_order,
                items=self.strict_items,
            )
