"""Cost 模块模型测试"""
from datetime import date
from decimal import Decimal

from django.core.exceptions import ValidationError
from django.db import IntegrityError
from django.test import TestCase

from basic_info import models as basic_models
from cost import models as cost_models


class CostCategoryModelTests(TestCase):
    def setUp(self):
        self.merchant = basic_models.Merchant.objects.create(
            name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
        )

    def test_str_representation(self):
        cat = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='electricity', name='电费',
        )
        self.assertEqual(str(cat), '电费 (electricity)')

    def test_unique_together_merchant_key(self):
        """同一商户 same unique_key 不可重复"""
        cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='electricity', name='电费',
        )
        with self.assertRaises(IntegrityError):
            cost_models.CostCategory.objects.create(
                merchant=self.merchant, unique_key='electricity', name='电费2',
            )

    def test_different_merchants_same_key_allowed(self):
        """不同商户 same unique_key 允许"""
        other = basic_models.Merchant.objects.create(
            name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
        )
        cat1 = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='electricity', name='电费',
        )
        cat2 = cost_models.CostCategory.objects.create(
            merchant=other, unique_key='electricity', name='电费',
        )
        self.assertNotEqual(cat1.id, cat2.id)

    def test_parent_self_reference(self):
        """父类目 self-FK"""
        parent = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='utilities', name='公共事业',
        )
        child = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='electricity', name='电费',
            parent=parent,
        )
        self.assertEqual(child.parent_id, parent.id)
        self.assertEqual(list(parent.children.all()), [child])

    def test_description_nullable(self):
        cat = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='key', name='name',
        )
        self.assertIsNone(cat.description)

    def test_ordering_by_merchant_then_name(self):
        other = basic_models.Merchant.objects.create(
            name='其他商户', type=basic_models.MerchantTypeEnum.STORE,
        )
        cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='z', name='Z类目',
        )
        cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='a', name='A类目',
        )
        cost_models.CostCategory.objects.create(
            merchant=other, unique_key='m', name='M类目',
        )
        # ordering = ('merchant', 'name') — 同一个 merchant 内按 name 排序
        own = cost_models.CostCategory.objects.filter(merchant=self.merchant)
        own_names = [c.name for c in own]
        self.assertEqual(own_names, ['A类目', 'Z类目'])  # name 升序


class CostEntryModelTests(TestCase):
    def setUp(self):
        self.merchant = basic_models.Merchant.objects.create(
            name='测试商户', type=basic_models.MerchantTypeEnum.STORE,
        )
        self.category = cost_models.CostCategory.objects.create(
            merchant=self.merchant, unique_key='transport', name='运输费',
        )

    def test_str_representation(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=Decimal('150.00'),
            occurred_at=date(2026, 6, 1),
        )
        self.assertIn('运输费', str(entry))
        self.assertIn('150.00', str(entry))

    def test_default_ordering_by_occurred_at_desc(self):
        e1 = cost_models.CostEntry.objects.create(
            merchant=self.merchant, category=self.category,
            amount=Decimal('100.00'), occurred_at=date(2026, 6, 1),
        )
        e2 = cost_models.CostEntry.objects.create(
            merchant=self.merchant, category=self.category,
            amount=Decimal('200.00'), occurred_at=date(2026, 6, 10),
        )
        entries = list(cost_models.CostEntry.objects.all())
        self.assertEqual(entries[0].id, e2.id)
        self.assertEqual(entries[1].id, e1.id)

    def test_operator_nullable(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=Decimal('100.00'),
            occurred_at=date(2026, 6, 1),
            operator=None,
        )
        self.assertIsNone(entry.operator_id)

    def test_source_fields_nullable(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=Decimal('100.00'),
            occurred_at=date(2026, 6, 1),
        )
        self.assertIsNone(entry.source_module)
        self.assertIsNone(entry.source_id)

    def test_image_fields_nullable(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=Decimal('100.00'),
            occurred_at=date(2026, 6, 1),
        )
        self.assertFalse(bool(entry.image1))
        self.assertFalse(bool(entry.image2))

    def test_formula_amount_calculated_on_create(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=None,
            unit_amount=Decimal('120.00'),
            quantity=Decimal('2.5'),
            unit_name='人天',
            occurred_at=date(2026, 6, 1),
        )
        self.assertEqual(entry.amount, Decimal('300.00'))
        self.assertEqual(entry.unit_name, '人天')

    def test_formula_amount_recalculated_on_update(self):
        entry = cost_models.CostEntry.objects.create(
            merchant=self.merchant,
            category=self.category,
            amount=Decimal('300.00'),
            unit_amount=Decimal('100.00'),
            quantity=Decimal('3'),
            occurred_at=date(2026, 6, 1),
        )
        entry.quantity = Decimal('4')
        entry.amount = Decimal('999.00')
        entry.save()
        entry.refresh_from_db()
        self.assertEqual(entry.amount, Decimal('400.00'))

    def test_formula_partial_fields_raise_validation_error(self):
        with self.assertRaises(ValidationError) as ctx:
            cost_models.CostEntry.objects.create(
                merchant=self.merchant,
                category=self.category,
                amount=None,
                unit_amount=Decimal('100.00'),
                quantity=None,
                occurred_at=date(2026, 6, 1),
            )
        self.assertIn('quantity', ctx.exception.message_dict)

    def test_manual_amount_required_without_formula(self):
        with self.assertRaises(ValidationError) as ctx:
            cost_models.CostEntry.objects.create(
                merchant=self.merchant,
                category=self.category,
                amount=None,
                occurred_at=date(2026, 6, 1),
            )
        self.assertIn('amount', ctx.exception.message_dict)
