"""
Unit tests for accounting custom exceptions.

Tests for task 1.2:
  - FinancialStatementImbalanceError (Requirements 5.3)
  - ReconciliationError (Requirements 6.3)
"""

from decimal import Decimal
from django.test import SimpleTestCase

from accounting.exceptions import FinancialStatementImbalanceError, ReconciliationError


class FinancialStatementImbalanceErrorTests(SimpleTestCase):
    """Tests for FinancialStatementImbalanceError."""

    def test_difference_stored_correctly(self):
        """difference attribute is stored exactly as passed."""
        diff = Decimal("150.75")
        error = FinancialStatementImbalanceError(diff)
        self.assertEqual(error.difference, diff)

    def test_difference_stored_for_zero(self):
        """difference attribute is stored when value is zero."""
        error = FinancialStatementImbalanceError(Decimal("0.00"))
        self.assertEqual(error.difference, Decimal("0.00"))

    def test_difference_stored_for_negative(self):
        """difference attribute is stored even when negative."""
        error = FinancialStatementImbalanceError(Decimal("-50.00"))
        self.assertEqual(error.difference, Decimal("-50.00"))

    def test_str_contains_difference(self):
        """str(error) includes the difference amount."""
        diff = Decimal("99.99")
        error = FinancialStatementImbalanceError(diff)
        self.assertIn(str(diff), str(error))

    def test_str_mentions_sfp_violation(self):
        """str(error) describes the SFP balance equation violation."""
        error = FinancialStatementImbalanceError(Decimal("1.00"))
        self.assertIn("SFP balance equation violated", str(error))

    def test_is_exception_subclass(self):
        """FinancialStatementImbalanceError is a subclass of Exception."""
        self.assertTrue(issubclass(FinancialStatementImbalanceError, Exception))

    def test_can_be_raised_and_caught(self):
        """FinancialStatementImbalanceError can be raised and caught."""
        with self.assertRaises(FinancialStatementImbalanceError) as ctx:
            raise FinancialStatementImbalanceError(Decimal("10.50"))
        self.assertEqual(ctx.exception.difference, Decimal("10.50"))


class ReconciliationErrorTests(SimpleTestCase):
    """Tests for ReconciliationError."""

    def _make_error(self, is_net_income="500.00", equity_change="480.00"):
        return ReconciliationError(
            Decimal(is_net_income), Decimal(equity_change)
        )

    def test_is_net_income_stored_correctly(self):
        """is_net_income attribute is stored exactly as passed."""
        error = self._make_error(is_net_income="1234.56", equity_change="1000.00")
        self.assertEqual(error.is_net_income, Decimal("1234.56"))

    def test_equity_change_stored_correctly(self):
        """equity_change attribute is stored exactly as passed."""
        error = self._make_error(is_net_income="500.00", equity_change="480.25")
        self.assertEqual(error.equity_change, Decimal("480.25"))

    def test_difference_computed_correctly(self):
        """difference == is_net_income - equity_change."""
        error = self._make_error(is_net_income="500.00", equity_change="480.00")
        self.assertEqual(error.difference, Decimal("20.00"))

    def test_difference_when_zero(self):
        """difference is zero when is_net_income equals equity_change."""
        error = self._make_error(is_net_income="300.00", equity_change="300.00")
        self.assertEqual(error.difference, Decimal("0.00"))

    def test_difference_when_negative(self):
        """difference can be negative when equity_change > is_net_income."""
        error = self._make_error(is_net_income="100.00", equity_change="200.00")
        self.assertEqual(error.difference, Decimal("-100.00"))

    def test_str_contains_is_net_income(self):
        """str(error) includes the IS net income figure."""
        error = self._make_error(is_net_income="500.00", equity_change="480.00")
        self.assertIn("500.00", str(error))

    def test_str_contains_equity_change(self):
        """str(error) includes the equity change figure."""
        error = self._make_error(is_net_income="500.00", equity_change="480.00")
        self.assertIn("480.00", str(error))

    def test_str_contains_difference(self):
        """str(error) includes the computed difference."""
        error = self._make_error(is_net_income="500.00", equity_change="480.00")
        self.assertIn("20.00", str(error))

    def test_is_exception_subclass(self):
        """ReconciliationError is a subclass of Exception."""
        self.assertTrue(issubclass(ReconciliationError, Exception))

    def test_can_be_raised_and_caught(self):
        """ReconciliationError can be raised and caught."""
        with self.assertRaises(ReconciliationError) as ctx:
            raise ReconciliationError(Decimal("750.00"), Decimal("600.00"))
        self.assertEqual(ctx.exception.is_net_income, Decimal("750.00"))
        self.assertEqual(ctx.exception.equity_change, Decimal("600.00"))
        self.assertEqual(ctx.exception.difference, Decimal("150.00"))
