"""
Tests for fiscal period management views.
"""

from django.test import TestCase, Client
from django.urls import reverse
from django.contrib.auth import get_user_model
from django.utils import timezone
from decimal import Decimal
from datetime import date, timedelta

from accounting.models import (
    FiscalPeriod, 
    JournalEntry, 
    JournalEntryLine, 
    Account,
    GeneralLedger
)
from users.models import Branch

User = get_user_model()


class FiscalPeriodListViewTest(TestCase):
    """
    Test fiscal_period_list view displays periods correctly.
    **Validates: Requirements 10.1, 10.2, 10.3**
    """
    
    def setUp(self):
        """Set up test data"""
        self.client = Client()
        
        # Create test user with staff status
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123',
            email='test@example.com',
            phone_number='0712345678',
            role='admin',  # Add admin role for staff access
            is_staff=True
        )
        self.client.login(username='testuser', password='testpass123')
        
        # Create test fiscal periods
        self.period1 = FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            status='open'
        )
        
        self.period2 = FiscalPeriod.objects.create(
            name='Q4 2023',
            period_type='quarterly',
            start_date=date(2023, 10, 1),
            end_date=date(2023, 12, 31),
            status='closed',
            closed_at=timezone.now(),
            closed_by=self.user
        )
        
        self.period3 = FiscalPeriod.objects.create(
            name='FY 2023',
            period_type='annual',
            start_date=date(2023, 1, 1),
            end_date=date(2023, 12, 31),
            status='closed',
            closed_at=timezone.now(),
            closed_by=self.user
        )
    
    def test_list_view_displays_all_periods(self):
        """Test that list view displays all fiscal periods"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'January 2024')
        self.assertContains(response, 'Q4 2023')
        self.assertContains(response, 'FY 2023')
    
    def test_list_view_sorted_by_start_date_descending(self):
        """Test that periods are sorted by start_date descending"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        periods = list(response.context['periods'])
        self.assertEqual(periods[0].name, 'January 2024')
        self.assertEqual(periods[1].name, 'Q4 2023')
        self.assertEqual(periods[2].name, 'FY 2023')
    
    def test_list_view_shows_period_type(self):
        """Test that period types are displayed"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertContains(response, 'Monthly')
        self.assertContains(response, 'Quarterly')
        self.assertContains(response, 'Annual')
    
    def test_list_view_shows_dates(self):
        """Test that start and end dates are displayed"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        # Check for date content (format may vary)
        self.assertContains(response, '2024')
        self.assertContains(response, '2023')
    
    def test_list_view_shows_status(self):
        """Test that status (open/closed) is displayed"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertContains(response, 'Open')
        self.assertContains(response, 'Closed')
    
    def test_list_view_summary_statistics(self):
        """Test that summary statistics are calculated correctly"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertEqual(response.context['total_periods'], 3)
        self.assertEqual(response.context['open_periods'], 1)
        self.assertEqual(response.context['closed_periods'], 2)
    
    def test_list_view_requires_login(self):
        """Test that list view requires authentication"""
        self.client.logout()
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login', response.url)
    
    def test_list_view_requires_staff_status(self):
        """Test that list view requires staff status"""
        # Create non-staff user
        non_staff_user = User.objects.create_user(
            username='nonstaff',
            password='testpass123',
            email='nonstaff@example.com',
            phone_number='0712345679',
            role='borrower',  # Borrower role (not staff)
            is_staff=False
        )
        self.client.login(username='nonstaff', password='testpass123')
        
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        # Should redirect or show permission denied
        self.assertNotEqual(response.status_code, 200)
    
    def test_list_view_has_create_button(self):
        """Test that list view has a Create Period button"""
        response = self.client.get(reverse('accounting:fiscal_period_list'))
        
        self.assertContains(response, 'Create Period')
        self.assertContains(response, reverse('accounting:fiscal_period_create'))


class FiscalPeriodDetailViewTest(TestCase):
    """
    Test fiscal_period_detail view displays period details.
    **Validates: Requirements 10.1, 10.2, 10.3**
    """
    
    def setUp(self):
        """Set up test data"""
        self.client = Client()
        
        # Create test user with staff status
        self.user = User.objects.create_user(
            username='testdetail',
            password='testpass123',
            email='testdetail@example.com',
            phone_number='0712345680',
            role='admin',  # Add admin role for staff access
            is_staff=True
        )
        self.client.login(username='testdetail', password='testpass123')
        
        # Create test period
        self.period = FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            status='open'
        )
    
    def test_detail_view_displays_period_info(self):
        """Test that detail view displays period information"""
        response = self.client.get(
            reverse('accounting:fiscal_period_detail', args=[self.period.id])
        )
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'January 2024')
        self.assertContains(response, 'monthly')
    
    def test_detail_view_shows_opening_balances(self):
        """Test that detail view displays opening balances if available"""
        # Add opening balances
        self.period.opening_balances = {
            '101001': {
                'account_id': 1,
                'account_name': 'Cash',
                'balance': '5000.00'
            }
        }
        self.period.save()
        
        response = self.client.get(
            reverse('accounting:fiscal_period_detail', args=[self.period.id])
        )
        
        self.assertContains(response, 'Opening Balances')
        self.assertContains(response, 'Cash')
        self.assertContains(response, '5000.00')
    
    def test_detail_view_shows_closing_balances_when_closed(self):
        """Test that detail view displays closing balances for closed periods"""
        # Close the period and add closing balances
        self.period.status = 'closed'
        self.period.closed_at = timezone.now()
        self.period.closed_by = self.user
        self.period.closing_balances = {
            '101001': {
                'account_id': 1,
                'account_name': 'Cash',
                'balance': '5500.00'
            }
        }
        self.period.save()
        
        response = self.client.get(
            reverse('accounting:fiscal_period_detail', args=[self.period.id])
        )
        
        self.assertContains(response, 'Closing Balances')
        self.assertContains(response, 'Cash')
        self.assertContains(response, '5500.00')
    
    def test_detail_view_requires_login(self):
        """Test that detail view requires authentication"""
        self.client.logout()
        response = self.client.get(
            reverse('accounting:fiscal_period_detail', args=[self.period.id])
        )
        
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login', response.url)


class FiscalPeriodCreateViewTest(TestCase):
    """
    Test fiscal_period_create view and FiscalPeriodForm validation.
    **Validates: Requirements 10.1, 10.2, 10.3**
    """
    
    def setUp(self):
        """Set up test data"""
        self.client = Client()
        
        # Create test user with staff status
        self.user = User.objects.create_user(
            username='testcreate',
            password='testpass123',
            email='testcreate@example.com',
            phone_number='0712345681',
            role='admin',  # Add admin role for staff access
            is_staff=True
        )
        self.client.login(username='testcreate', password='testpass123')
    
    def test_create_view_displays_form(self):
        """Test that create view displays the form"""
        response = self.client.get(reverse('accounting:fiscal_period_create'))
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'Create Fiscal Period')
        self.assertContains(response, 'name')
        self.assertContains(response, 'period_type')
        self.assertContains(response, 'start_date')
        self.assertContains(response, 'end_date')
    
    def test_create_period_successfully(self):
        """Test creating a new fiscal period"""
        data = {
            'name': 'February 2024',
            'period_type': 'monthly',
            'start_date': '2024-02-01',
            'end_date': '2024-02-29',
        }
        
        response = self.client.post(
            reverse('accounting:fiscal_period_create'),
            data=data,
            follow=False
        )
        
        # Should redirect to detail view
        self.assertEqual(response.status_code, 302)
        
        # Check period was created
        period = FiscalPeriod.objects.get(name='February 2024')
        self.assertEqual(period.period_type, 'monthly')
        self.assertEqual(period.status, 'open')
    
    def test_form_validates_end_date_after_start_date(self):
        """Test that form validates end_date is after start_date"""
        data = {
            'name': 'Invalid Period',
            'period_type': 'monthly',
            'start_date': '2024-02-29',
            'end_date': '2024-02-01',  # Before start date
        }
        
        response = self.client.post(
            reverse('accounting:fiscal_period_create'),
            data=data
        )
        
        # Should show form with errors
        self.assertEqual(response.status_code, 200)
        self.assertFormError(
            response.context['form'],
            'end_date',
            'End date must be after start date.'
        )
    
    def test_form_validates_no_overlapping_periods(self):
        """Test that form validates period doesn't overlap with existing periods"""
        # Create existing period
        FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
        )
        
        # Try to create overlapping period
        data = {
            'name': 'Overlapping Period',
            'period_type': 'monthly',
            'start_date': '2024-01-15',  # Overlaps with January
            'end_date': '2024-02-15',
        }
        
        response = self.client.post(
            reverse('accounting:fiscal_period_create'),
            data=data
        )
        
        # Should show form with errors
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'overlaps')
    
    def test_create_view_requires_login(self):
        """Test that create view requires authentication"""
        self.client.logout()
        response = self.client.get(reverse('accounting:fiscal_period_create'))
        
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login', response.url)


class FiscalPeriodCloseViewTest(TestCase):
    """
    Test fiscal_period_close view functionality.
    **Validates: Requirements 10.5, 10.6, 10.7, 10.8**
    """
    
    def setUp(self):
        """Set up test data"""
        self.client = Client()
        
        # Create test user with staff status
        self.user = User.objects.create_user(
            username='testclose',
            password='testpass123',
            email='testclose@example.com',
            phone_number='0712345682',
            role='admin',  # Add admin role for staff access
            is_staff=True
        )
        self.client.login(username='testclose', password='testpass123')
        
        # Create branch
        self.branch = Branch.objects.create(
            name='Main Branch',
            code='MB001'
        )
        
        # Create test period
        self.period = FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            status='open'
        )
        
        # Create test accounts
        self.cash_account = Account.objects.create(
            code='101001',
            name='Cash',
            account_type='asset',
            description='Cash account',
            created_by=self.user,
            is_active=True
        )
        
        self.revenue_account = Account.objects.create(
            code='401001',
            name='Service Revenue',
            account_type='income',
            description='Revenue from services',
            created_by=self.user,
            is_active=True
        )
    
    def test_close_view_prevents_closing_with_draft_entries(self):
        """Test that closing is prevented when draft entries exist"""
        # Create a draft journal entry
        entry = JournalEntry.objects.create(
            reference_number='JE-20240101-001',
            transaction_date=date(2024, 1, 15),
            description='Test entry',
            status='draft',
            branch=self.branch,
            created_by=self.user
        )
        
        # Try to close the period (confirmation GET request should show draft warning)
        response = self.client.get(
            reverse('accounting:fiscal_period_close', args=[self.period.id])
        )
        
        self.assertEqual(response.status_code, 200)
        self.assertContains(response, 'draft')
        
        # Period should still be open
        self.period.refresh_from_db()
        self.assertEqual(self.period.status, 'open')
    
    def test_close_view_calculates_closing_balances(self):
        """Test that closing calculates and stores closing balances"""
        # Create and post a journal entry
        entry = JournalEntry.objects.create(
            reference_number='JE-20240115-001',
            transaction_date=date(2024, 1, 15),
            description='Test entry',
            status='posted',
            branch=self.branch,
            created_by=self.user,
            posted_by=self.user,
            posted_at=timezone.now()
        )
        
        # Create lines
        line1 = JournalEntryLine.objects.create(
            journal_entry=entry,
            account=self.cash_account,
            description='Cash received',
            debit_amount=Decimal('1000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        
        line2 = JournalEntryLine.objects.create(
            journal_entry=entry,
            account=self.revenue_account,
            description='Service revenue',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('1000.00'),
            line_number=2
        )
        
        # Create general ledger entries
        GeneralLedger.objects.create(
            account=self.cash_account,
            journal_entry=entry,
            journal_entry_line=line1,
            transaction_date=entry.transaction_date,
            description='Cash received',
            reference_number=entry.reference_number,
            debit_amount=Decimal('1000.00'),
            credit_amount=Decimal('0.00'),
            balance=Decimal('1000.00'),
            branch=self.branch,
            posted_at=timezone.now(),
            posted_by=self.user
        )
        
        GeneralLedger.objects.create(
            account=self.revenue_account,
            journal_entry=entry,
            journal_entry_line=line2,
            transaction_date=entry.transaction_date,
            description='Service revenue',
            reference_number=entry.reference_number,
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('1000.00'),
            balance=Decimal('1000.00'),
            branch=self.branch,
            posted_at=timezone.now(),
            posted_by=self.user
        )
        
        # Close the period
        response = self.client.post(
            reverse('accounting:fiscal_period_close', args=[self.period.id]),
            follow=False
        )
        
        # Should redirect to detail view
        self.assertEqual(response.status_code, 302)
        
        # Check period is closed
        self.period.refresh_from_db()
        self.assertEqual(self.period.status, 'closed')
        self.assertIsNotNone(self.period.closed_at)
        self.assertEqual(self.period.closed_by, self.user)
        
        # Check closing balances were calculated
        self.assertIn('101001', self.period.closing_balances)
        self.assertIn('401001', self.period.closing_balances)
    
    def test_close_view_sets_opening_balances_for_next_period(self):
        """Test that closing sets opening balances for next period"""
        # Create next period
        next_period = FiscalPeriod.objects.create(
            name='February 2024',
            period_type='monthly',
            start_date=date(2024, 2, 1),
            end_date=date(2024, 2, 29),
            status='open'
        )
        
        # Create and post entry to set some balances
        entry = JournalEntry.objects.create(
            reference_number='JE-20240115-001',
            transaction_date=date(2024, 1, 15),
            description='Test entry',
            status='posted',
            branch=self.branch,
            created_by=self.user,
            posted_by=self.user,
            posted_at=timezone.now()
        )
        
        line1 = JournalEntryLine.objects.create(
            journal_entry=entry,
            account=self.cash_account,
            description='Cash',
            debit_amount=Decimal('1000.00'),
            credit_amount=Decimal('0.00'),
            line_number=1
        )
        
        line2 = JournalEntryLine.objects.create(
            journal_entry=entry,
            account=self.revenue_account,
            description='Revenue',
            debit_amount=Decimal('0.00'),
            credit_amount=Decimal('1000.00'),
            line_number=2
        )
        
        GeneralLedger.objects.create(
            account=self.cash_account,
            journal_entry=entry,
            journal_entry_line=line1,
            transaction_date=entry.transaction_date,
            description='Cash',
            reference_number=entry.reference_number,
            debit_amount=Decimal('1000.00'),
            credit_amount=Decimal('0.00'),
            balance=Decimal('1000.00'),
            branch=self.branch,
            posted_at=timezone.now(),
            posted_by=self.user
        )
        
        # Close the period
        response = self.client.post(
            reverse('accounting:fiscal_period_close', args=[self.period.id])
        )
        
        # Check next period has opening balances
        next_period.refresh_from_db()
        self.assertIsNotNone(next_period.opening_balances)
        self.assertEqual(
            next_period.opening_balances,
            self.period.closing_balances
        )
    
    def test_close_view_requires_login(self):
        """Test that close view requires authentication"""
        self.client.logout()
        response = self.client.post(
            reverse('accounting:fiscal_period_close', args=[self.period.id])
        )
        
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login', response.url)
    
    def test_cannot_close_already_closed_period(self):
        """Test that already closed periods cannot be closed again"""
        # Close the period first
        self.period.status = 'closed'
        self.period.closed_at = timezone.now()
        self.period.closed_by = self.user
        self.period.save()
        
        # Try to close again
        response = self.client.post(
            reverse('accounting:fiscal_period_close', args=[self.period.id])
        )
        
        # Should redirect with error message
        self.assertEqual(response.status_code, 302)


class FiscalPeriodReopenViewTest(TestCase):
    """
    Test fiscal_period_reopen view functionality.
    **Validates: Requirements 10.9**
    """
    
    def setUp(self):
        """Set up test data"""
        self.client = Client()
        
        # Create test user with staff status and reopen permission
        self.user = User.objects.create_user(
            username='testreopen',
            password='testpass123',
            email='testreopen@example.com',
            phone_number='0712345683',
            role='admin',  # Add admin role for staff access
            is_staff=True
        )
        
        # Add reopen permission
        from django.contrib.auth.models import Permission
        from django.contrib.contenttypes.models import ContentType
        
        content_type = ContentType.objects.get_for_model(FiscalPeriod)
        permission, created = Permission.objects.get_or_create(
            codename='reopen_fiscal_period',
            name='Can reopen fiscal period',
            content_type=content_type,
        )
        self.user.user_permissions.add(permission)
        
        self.client.login(username='testreopen', password='testpass123')
        
        # Create closed period
        self.period = FiscalPeriod.objects.create(
            name='January 2024',
            period_type='monthly',
            start_date=date(2024, 1, 1),
            end_date=date(2024, 1, 31),
            status='closed',
            closed_at=timezone.now(),
            closed_by=self.user,
            closing_balances={'101001': {'balance': '1000.00'}}
        )
    
    def test_reopen_view_reopens_period(self):
        """Test that reopen view successfully reopens a closed period"""
        response = self.client.post(
            reverse('accounting:fiscal_period_reopen', args=[self.period.id]),
            follow=False
        )
        
        # Should redirect to detail view
        self.assertEqual(response.status_code, 302)
        
        # Check period is reopened
        self.period.refresh_from_db()
        self.assertEqual(self.period.status, 'open')
        self.assertIsNone(self.period.closed_at)
        self.assertIsNone(self.period.closed_by)
    
    def test_reopen_view_requires_permission(self):
        """Test that reopen requires reopen_fiscal_period permission"""
        # Create user without permission
        user_no_perm = User.objects.create_user(
            username='noperm',
            password='testpass123',
            email='noperm@example.com',
            phone_number='0712345684',
            role='admin',  # Admin role but without specific permission
            is_staff=True
        )
        self.client.login(username='noperm', password='testpass123')
        
        response = self.client.post(
            reverse('accounting:fiscal_period_reopen', args=[self.period.id])
        )
        
        # Should redirect with error
        self.assertEqual(response.status_code, 302)
        
        # Period should still be closed
        self.period.refresh_from_db()
        self.assertEqual(self.period.status, 'closed')
    
    def test_cannot_reopen_already_open_period(self):
        """Test that already open periods cannot be reopened"""
        # Change period to open
        self.period.status = 'open'
        self.period.closed_at = None
        self.period.closed_by = None
        self.period.save()
        
        response = self.client.post(
            reverse('accounting:fiscal_period_reopen', args=[self.period.id])
        )
        
        # Should redirect with error message
        self.assertEqual(response.status_code, 302)
    
    def test_reopen_view_requires_login(self):
        """Test that reopen view requires authentication"""
        self.client.logout()
        response = self.client.post(
            reverse('accounting:fiscal_period_reopen', args=[self.period.id])
        )
        
        self.assertEqual(response.status_code, 302)
        self.assertIn('/login', response.url)
