"""
Тесты для ProductCostCalculator - расчет себестоимости товаров на основе партий.

Тестируемая функциональность:
- Расчет средневзвешенной стоимости из партий
- Автоматическое обновление стоимости при изменении партий
- Получение детальной информации о расчете
- Интеграция с Django signals
"""

from decimal import Decimal
from django.test import TestCase
from django.db import connection

from products.models import Product
from products.services.cost_calculator import ProductCostCalculator
from inventory.models import Warehouse, StockBatch


class ProductCostCalculatorTest(TestCase):
    """Тесты для ProductCostCalculator - unit тесты без signals."""

    def setUp(self):
        """Подготовка тестовых данных."""
        # Создаем товар (без категорий - они не нужны для тестов себестоимости)
        self.product = Product.objects.create(
            name='Тестовый товар',
            sku='TEST-001',
            cost_price=Decimal('0.00'),
            price=Decimal('200.00'),
            unit='шт'
        )

        # Создаем склад
        self.warehouse = Warehouse.objects.create(
            name='Тестовый склад',
            description='Склад для тестов'
        )

    def test_calculate_weighted_average_cost_no_batches(self):
        """Тест: товар без партий -> стоимость 0.00"""
        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        self.assertEqual(cost, Decimal('0.00'))

    def test_calculate_weighted_average_cost_single_batch(self):
        """Тест: одна партия -> стоимость партии"""
        # Создаем партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        self.assertEqual(cost, Decimal('100.00'))

    def test_calculate_weighted_average_cost_multiple_batches_same_price(self):
        """Тест: несколько партий с одинаковой ценой -> та же цена"""
        # Создаем партии
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('5.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        self.assertEqual(cost, Decimal('100.00'))

    def test_calculate_weighted_average_cost_multiple_batches_different_price(self):
        """Тест: несколько партий с разной ценой -> средневзвешенная"""
        # Создаем партии
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('120.00'),
            is_active=True
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        # (10*100 + 10*120) / 20 = 2200 / 20 = 110.00
        self.assertEqual(cost, Decimal('110.00'))

    def test_calculate_weighted_average_cost_complex_case(self):
        """Тест: сложный случай с тремя партиями разного объема"""
        # Создаем партии
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('5.000'),
            cost_price=Decimal('80.00'),
            is_active=True
        )
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('15.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('120.00'),
            is_active=True
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        # (5*80 + 15*100 + 10*120) / 30 = (400 + 1500 + 1200) / 30 = 3100 / 30 = 103.33
        self.assertEqual(cost, Decimal('103.33'))

    def test_calculate_weighted_average_cost_ignores_inactive_batches(self):
        """Тест: неактивные партии не учитываются"""
        # Создаем активную партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        # Создаем неактивную партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('200.00'),
            is_active=False  # Неактивна!
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        # Должна учитываться только активная партия
        self.assertEqual(cost, Decimal('100.00'))

    def test_calculate_weighted_average_cost_ignores_zero_quantity_batches(self):
        """Тест: партии с нулевым количеством не учитываются"""
        # Создаем партию с товаром
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        # Создаем пустую партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('0.000'),  # Пустая!
            cost_price=Decimal('200.00'),
            is_active=True
        )

        cost = ProductCostCalculator.calculate_weighted_average_cost(self.product)

        # Должна учитываться только непустая партия
        self.assertEqual(cost, Decimal('100.00'))

    def test_update_product_cost_updates_field(self):
        """Тест: update_product_cost обновляет поле cost_price в БД"""
        # Создаем партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('150.00'),
            is_active=True
        )

        # Убеждаемся что текущая стоимость 0
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

        # Обновляем стоимость
        old_cost, new_cost, was_updated = ProductCostCalculator.update_product_cost(
            self.product,
            save=True
        )

        # Проверяем результат
        self.assertEqual(old_cost, Decimal('0.00'))
        self.assertEqual(new_cost, Decimal('150.00'))
        self.assertTrue(was_updated)

        # Перезагружаем товар из БД
        self.product.refresh_from_db()

        # Проверяем что стоимость обновилась в БД
        self.assertEqual(self.product.cost_price, Decimal('150.00'))

    def test_update_product_cost_no_save(self):
        """Тест: update_product_cost с save=False не сохраняет в БД"""
        # Создаем партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('150.00'),
            is_active=True
        )

        # Обновляем стоимость БЕЗ сохранения
        old_cost, new_cost, was_updated = ProductCostCalculator.update_product_cost(
            self.product,
            save=False  # Не сохраняем!
        )

        # Проверяем результат операции
        self.assertTrue(was_updated)
        self.assertEqual(new_cost, Decimal('150.00'))

        # Проверяем что в памяти обновилось
        self.assertEqual(self.product.cost_price, Decimal('150.00'))

        # Перезагружаем товар из БД
        self.product.refresh_from_db()

        # Проверяем что в БД НЕ обновилось
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

    def test_update_product_cost_no_change(self):
        """Тест: update_product_cost возвращает was_updated=False если стоимость не изменилась"""
        # Устанавливаем начальную стоимость
        self.product.cost_price = Decimal('100.00')
        self.product.save()

        # Создаем партию с такой же стоимостью
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        # Обновляем стоимость
        old_cost, new_cost, was_updated = ProductCostCalculator.update_product_cost(
            self.product,
            save=True
        )

        # Проверяем что изменений не было
        self.assertFalse(was_updated)
        self.assertEqual(old_cost, Decimal('100.00'))
        self.assertEqual(new_cost, Decimal('100.00'))

    def test_get_cost_details(self):
        """Тест: get_cost_details возвращает детальную информацию"""
        # Устанавливаем начальную стоимость
        self.product.cost_price = Decimal('100.00')
        self.product.save()

        # Создаем партии
        batch1 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        batch2 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('5.000'),
            cost_price=Decimal('120.00'),
            is_active=True
        )

        # Получаем детали
        details = ProductCostCalculator.get_cost_details(self.product)

        # Проверяем структуру
        self.assertIn('cached_cost', details)
        self.assertIn('calculated_cost', details)
        self.assertIn('is_synced', details)
        self.assertIn('total_quantity', details)
        self.assertIn('batches', details)

        # Проверяем значения
        self.assertEqual(details['cached_cost'], Decimal('100.00'))
        self.assertEqual(details['calculated_cost'], Decimal('106.67'))  # (10*100 + 5*120) / 15
        self.assertFalse(details['is_synced'])  # Рассчитанная != кешированной
        self.assertEqual(details['total_quantity'], Decimal('15.000'))
        self.assertEqual(len(details['batches']), 2)

        # Проверяем детали партий
        batch_details = details['batches']
        self.assertEqual(batch_details[0]['warehouse_name'], self.warehouse.name)
        self.assertEqual(batch_details[0]['quantity'], Decimal('10.000'))
        self.assertEqual(batch_details[0]['cost_price'], Decimal('100.00'))
        self.assertEqual(batch_details[0]['total_value'], Decimal('1000.00'))

    def test_get_cost_details_synced(self):
        """Тест: get_cost_details показывает is_synced=True когда стоимости совпадают"""
        # Создаем партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        # Обновляем стоимость товара
        ProductCostCalculator.update_product_cost(self.product, save=True)

        # Получаем детали
        details = ProductCostCalculator.get_cost_details(self.product)

        # Проверяем синхронизацию
        self.assertTrue(details['is_synced'])
        self.assertEqual(details['cached_cost'], Decimal('100.00'))
        self.assertEqual(details['calculated_cost'], Decimal('100.00'))


class ProductCostCalculatorIntegrationTest(TestCase):
    """Интеграционные тесты с Django signals - проверка автоматического обновления."""

    def setUp(self):
        """Подготовка тестовых данных."""
        # Создаем товар (без категорий - они не нужны для тестов себестоимости)
        self.product = Product.objects.create(
            name='Тестовый товар',
            sku='TEST-INT-001',
            cost_price=Decimal('0.00'),
            price=Decimal('200.00'),
            unit='шт'
        )

        # Создаем склад
        self.warehouse = Warehouse.objects.create(
            name='Тестовый склад',
            description='Склад для интеграционных тестов'
        )

    def test_signal_updates_cost_on_batch_create(self):
        """Тест: создание партии автоматически обновляет себестоимость через signal"""
        # Проверяем начальную стоимость
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

        # Создаем партию (должен сработать signal)
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('150.00'),
            is_active=True
        )

        # Перезагружаем товар из БД
        self.product.refresh_from_db()

        # Проверяем что стоимость автоматически обновилась
        self.assertEqual(self.product.cost_price, Decimal('150.00'))

    def test_signal_updates_cost_on_batch_update(self):
        """Тест: изменение партии автоматически обновляет себестоимость"""
        # Создаем партию
        batch = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        # Перезагружаем товар
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('100.00'))

        # Изменяем стоимость партии
        batch.cost_price = Decimal('120.00')
        batch.save()

        # Перезагружаем товар
        self.product.refresh_from_db()

        # Проверяем что стоимость автоматически обновилась
        self.assertEqual(self.product.cost_price, Decimal('120.00'))

    def test_signal_updates_cost_on_batch_delete(self):
        """Тест: удаление партии автоматически обновляет себестоимость"""
        # Создаем две партии
        batch1 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        batch2 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('120.00'),
            is_active=True
        )

        # Перезагружаем товар
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('110.00'))  # Средневзвешенная

        # Удаляем одну партию
        batch2.delete()

        # Перезагружаем товар
        self.product.refresh_from_db()

        # Проверяем что стоимость пересчиталась
        self.assertEqual(self.product.cost_price, Decimal('100.00'))

    def test_signal_updates_cost_to_zero_when_all_batches_deleted(self):
        """Тест: удаление всех партий обнуляет себестоимость"""
        # Создаем партию
        batch = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        # Перезагружаем товар
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('100.00'))

        # Удаляем партию
        batch.delete()

        # Перезагружаем товар
        self.product.refresh_from_db()

        # Проверяем что стоимость обнулилась
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

    def test_lifecycle_scenario(self):
        """Тест: полный жизненный цикл товара с партиями"""
        # Шаг 1: Товар создан, партий нет
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

        # Шаг 2: Первая поставка
        batch1 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('20.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('100.00'))

        # Шаг 3: Вторая поставка по другой цене
        batch2 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('120.00'),
            is_active=True
        )
        self.product.refresh_from_db()
        # (20*100 + 10*120) / 30 = 3200 / 30 = 106.67
        self.assertEqual(self.product.cost_price, Decimal('106.67'))

        # Шаг 4: Товар продали (обнуляем количество в партиях)
        batch1.quantity = Decimal('0.000')
        batch1.save()
        batch2.quantity = Decimal('0.000')
        batch2.save()
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('0.00'))

        # Шаг 5: Новая поставка после опустошения
        batch3 = StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('15.000'),
            cost_price=Decimal('130.00'),
            is_active=True
        )
        self.product.refresh_from_db()
        self.assertEqual(self.product.cost_price, Decimal('130.00'))


class ProductCostDetailsPropertyTest(TestCase):
    """Тесты для property cost_price_details в модели Product."""

    def setUp(self):
        """Подготовка тестовых данных."""
        self.product = Product.objects.create(
            name='Тестовый товар',
            sku='TEST-PROP-001',
            cost_price=Decimal('0.00'),
            price=Decimal('200.00'),
            unit='шт'
        )

        self.warehouse = Warehouse.objects.create(
            name='Тестовый склад',
            description='Склад для тестов property'
        )

    def test_cost_price_details_property_exists(self):
        """Тест: property cost_price_details существует"""
        self.assertTrue(hasattr(self.product, 'cost_price_details'))

    def test_cost_price_details_returns_dict(self):
        """Тест: property возвращает словарь с нужными ключами"""
        details = self.product.cost_price_details

        self.assertIsInstance(details, dict)
        self.assertIn('cached_cost', details)
        self.assertIn('calculated_cost', details)
        self.assertIn('is_synced', details)
        self.assertIn('total_quantity', details)
        self.assertIn('batches', details)

    def test_cost_price_details_with_batches(self):
        """Тест: property правильно отображает информацию о партиях"""
        # Создаем партию
        StockBatch.objects.create(
            product=self.product,
            warehouse=self.warehouse,
            quantity=Decimal('10.000'),
            cost_price=Decimal('100.00'),
            is_active=True
        )

        details = self.product.cost_price_details

        self.assertEqual(len(details['batches']), 1)
        self.assertEqual(details['batches'][0]['warehouse_name'], self.warehouse.name)
        self.assertEqual(details['batches'][0]['quantity'], Decimal('10.000'))
        self.assertEqual(details['batches'][0]['cost_price'], Decimal('100.00'))
