# This file is part of Tradon. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.

from datetime import date
from decimal import Decimal

from trytond.exceptions import UserError
from trytond.modules.company.tests import create_company, set_company
from trytond.modules.currency.tests import create_currency
from trytond.pool import Pool
from trytond.tests.test_tryton import ModuleTestCase, with_transaction


class TradeFinanceTestCase(ModuleTestCase):
    'Test Trade Finance module'
    module = 'trade_finance'

    def create_facility(self):
        pool = Pool()
        Bank = pool.get('bank')
        Facility = pool.get('trade_finance.facility')
        Party = pool.get('party.party')

        currency = create_currency('USD')
        party = Party(name='Test Bank')
        party.save()
        bank = Bank(party=party)
        bank.save()
        facility = Facility(
            name='Test Facility',
            tfe=bank,
            currency=currency,
            commitment_status='committed',
            date_from=date(2026, 1, 1),
            date_to=date(2026, 12, 31))
        facility.save()
        return facility, currency

    def create_trade_finance_case(self):
        pool = Pool()
        Cost = pool.get('trade_finance.facility_limit_cost')
        Haircut = pool.get('trade_finance.facility_limit_haircut')
        Limit = pool.get('trade_finance.facility_limit')
        TradeFinance = pool.get('trade.finance')

        facility, currency = self.create_facility()
        company = create_company(currency=currency)
        limit, = Limit.create([{
                    'facility': facility.id,
                    'name': 'Prefi limit',
                    'currency': currency.id,
                    'amount': Decimal('1000000.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        Haircut.create([{
                    'limit': limit.id,
                    'attribute': 'commodity',
                    'haircut_pct': Decimal('20.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        Cost.create([{
                    'limit': limit.id,
                    'cost_type': 'interest',
                    'spread': Decimal('10.0000'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        with set_company(company):
            trade_finance, = TradeFinance.create([{
                        'bank': facility.tfe.id,
                        'company': company.id,
                        'currency': currency.id,
                        'start_date': date(2026, 1, 1),
                        }])
        return trade_finance, limit, currency

    @with_transaction()
    def test_sublimit_inherits_currency_and_dates(self):
        'Test sublimit inherits currency and dates from parent'
        pool = Pool()
        Limit = pool.get('trade_finance.facility_limit')

        facility, currency = self.create_facility()
        root, = Limit.create([{
                    'facility': facility.id,
                    'name': 'Global limit',
                    'currency': currency.id,
                    'amount': Decimal('100.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        child, = Limit.create([{
                    'parent': root.id,
                    'name': 'Trading limit',
                    'amount': Decimal('50.00'),
                    }])

        self.assertEqual(child.facility, facility)
        self.assertEqual(child.currency, currency)
        self.assertEqual(child.date_from, root.date_from)
        self.assertEqual(child.date_to, root.date_to)

    @with_transaction()
    def test_facility_limits_only_returns_root_limits(self):
        'Test facility limits exclude nested sublimits'
        pool = Pool()
        Facility = pool.get('trade_finance.facility')
        Limit = pool.get('trade_finance.facility_limit')

        facility, currency = self.create_facility()
        root, = Limit.create([{
                    'facility': facility.id,
                    'name': 'Global limit',
                    'currency': currency.id,
                    'amount': Decimal('100.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        child, = Limit.create([{
                    'parent': root.id,
                    'name': 'Trading limit',
                    'amount': Decimal('50.00'),
                    }])

        facility = Facility(facility.id)
        root = Limit(root.id)

        self.assertEqual(list(facility.limits), [root])
        self.assertEqual(list(root.children), [child])

    @with_transaction()
    def test_sublimit_dates_must_stay_within_parent(self):
        'Test sublimit dates stay within parent dates'
        pool = Pool()
        Limit = pool.get('trade_finance.facility_limit')

        facility, currency = self.create_facility()
        root, = Limit.create([{
                    'facility': facility.id,
                    'name': 'Global limit',
                    'currency': currency.id,
                    'amount': Decimal('100.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])

        with self.assertRaises(UserError):
            Limit.create([{
                        'parent': root.id,
                        'name': 'Late limit',
                        'amount': Decimal('50.00'),
                        'date_from': date(2026, 1, 1),
                        'date_to': date(2027, 1, 1),
                        }])

    @with_transaction()
    def test_currency_change_cascades_to_sublimits(self):
        'Test root currency changes cascade to sublimits'
        pool = Pool()
        Limit = pool.get('trade_finance.facility_limit')

        facility, currency = self.create_facility()
        other_currency = create_currency('EUR')
        root, = Limit.create([{
                    'facility': facility.id,
                    'name': 'Global limit',
                    'currency': currency.id,
                    'amount': Decimal('100.00'),
                    'date_from': date(2026, 1, 1),
                    'date_to': date(2026, 12, 31),
                    }])
        child, = Limit.create([{
                    'parent': root.id,
                    'name': 'Trading limit',
                    'amount': Decimal('50.00'),
                    }])

        Limit.write([root], {'currency': other_currency.id})
        child = Limit(child.id)

        self.assertEqual(child.currency, other_currency)

    @with_transaction()
    def test_trade_finance_number_sequence(self):
        'Test trade finance number sequence'
        trade_finance, _, _ = self.create_trade_finance_case()

        self.assertTrue(trade_finance.number.startswith('TFR-'))

    @with_transaction()
    def test_post_presentation_uses_facility_limit_ledger(self):
        'Test posting creates an audited facility limit movement'
        pool = Pool()
        Presentation = pool.get('trade.finance.presentation')
        Line = pool.get('trade.finance.presentation.line')

        trade_finance, limit, currency = self.create_trade_finance_case()
        presentation, = Presentation.create([{
                    'trade_finance': trade_finance.id,
                    'presentation_date': date(2026, 1, 10),
                    'presentation_type': 'prefi',
                    'finance_stage': 'prefi',
                    }])
        line, = Line.create([{
                    'presentation': presentation.id,
                    'facility_limit': limit.id,
                    'signed_quantity': Decimal('1000.0000'),
                    'commodity_price': Decimal('100.000000'),
                    }])

        self.assertEqual(line.currency, currency)
        self.assertEqual(line.gross_collateral_value, Decimal('100000.0000000000'))
        self.assertEqual(line.financed_amount, Decimal('100000.0000000000'))
        Presentation.post([presentation])

        presentation = Presentation(presentation.id)
        line, = presentation.lines
        self.assertEqual(presentation.state, 'posted')
        self.assertEqual(line.haircut_percent_applied, Decimal('20.00'))
        self.assertEqual(line.interest_rate_applied, Decimal('10.0000'))
        self.assertEqual(line.day_count_basis_applied, '360')
        self.assertEqual(line.financed_amount, Decimal('80000.000000000000'))
        self.assertEqual(
            line.signed_financed_amount, Decimal('80000.000000000000'))

    @with_transaction()
    def test_post_presentation_rejects_limit_outside_validity(self):
        'Test posting validates facility limit validity dates'
        pool = Pool()
        Presentation = pool.get('trade.finance.presentation')
        Line = pool.get('trade.finance.presentation.line')

        trade_finance, limit, _ = self.create_trade_finance_case()
        presentation, = Presentation.create([{
                    'trade_finance': trade_finance.id,
                    'presentation_date': date(2025, 12, 31),
                    'presentation_type': 'prefi',
                    'finance_stage': 'prefi',
                    }])
        Line.create([{
                    'presentation': presentation.id,
                    'facility_limit': limit.id,
                    'movement_date': date(2025, 12, 31),
                    'finance_stage': 'prefi',
                    'currency': trade_finance.currency.id,
                    'signed_financed_amount': Decimal('100.00'),
                    }])

        with self.assertRaises(UserError):
            Presentation.post([presentation])

    @with_transaction()
    def test_cancel_presentation_creates_reversal(self):
        'Test cancellation creates a posted reversal movement'
        pool = Pool()
        Presentation = pool.get('trade.finance.presentation')
        Line = pool.get('trade.finance.presentation.line')

        trade_finance, limit, _ = self.create_trade_finance_case()
        presentation, = Presentation.create([{
                    'trade_finance': trade_finance.id,
                    'presentation_date': date(2026, 1, 10),
                    'presentation_type': 'prefi',
                    'finance_stage': 'prefi',
                    }])
        Line.create([{
                    'presentation': presentation.id,
                    'facility_limit': limit.id,
                    'movement_date': date(2026, 1, 10),
                    'finance_stage': 'prefi',
                    'currency': trade_finance.currency.id,
                    'signed_financed_amount': Decimal('80000.00'),
                    }])
        Presentation.post([presentation])
        Presentation.cancel([presentation])

        presentations = Presentation.search([
                ('trade_finance', '=', trade_finance.id),
                ], order=[('sequence', 'ASC')])
        self.assertEqual(len(presentations), 2)
        self.assertEqual(presentations[0].state, 'cancelled')
        self.assertEqual(presentations[1].state, 'posted')
        reversal_line, = presentations[1].lines
        self.assertEqual(reversal_line.signed_financed_amount, Decimal('-80000.00'))

        trade_finance = pool.get('trade.finance')(trade_finance.id)
        self.assertEqual(trade_finance.exposure_amount, Decimal('0.00'))

    @with_transaction()
    def test_create_financing_wizard(self):
        'Test single input financing creation wizard'
        pool = Pool()
        TradeFinance = pool.get('trade.finance')
        Wizard = pool.get('trade.finance.create', type='wizard')

        _, limit, _ = self.create_trade_finance_case()
        company = create_company(currency=limit.currency)
        session_id, start_state, _ = Wizard.create()
        Wizard.execute(session_id, {
                start_state: {
                    'facility_limit': limit.id,
                    'company': company.id,
                    'bank_reference': 'BANK/TEST/001',
                    'start_date': date(2026, 1, 10),
                    'presentation_date': date(2026, 1, 10),
                    'presentation_type': 'prefi',
                    'finance_stage': 'prefi',
                    'signed_quantity': Decimal('1000.0000'),
                    'commodity_price': Decimal('100.000000'),
                    'signed_financed_amount': Decimal('80000.00'),
                    'post': True,
                    },
                }, 'create_')

        trade_finance, = TradeFinance.search([
                ('bank_reference', '=', 'BANK/TEST/001'),
                ])
        presentation, = trade_finance.presentations
        line, = presentation.lines
        self.assertEqual(trade_finance.bank, limit.facility.tfe)
        self.assertEqual(trade_finance.currency, limit.currency)
        self.assertEqual(presentation.state, 'posted')
        self.assertEqual(line.facility_limit, limit)


del ModuleTestCase
