"""
Tests for MarkiTect finance models and database schema.

This module tests the complete finance schema including:
- Database table creation and relationships
- Data integrity constraints
- Index performance
- Schema validation
- Migration functionality
"""

import pytest
import tempfile
import os
from datetime import date, datetime
from decimal import Decimal

from markitect.finance.models import FinanceModels


class TestFinanceModels:
    """Test suite for finance database models."""

    @pytest.fixture
    def temp_db(self):
        """Create temporary database for testing."""
        fd, path = tempfile.mkstemp(suffix='.db')
        os.close(fd)
        yield path
        os.unlink(path)

    @pytest.fixture
    def finance_models(self, temp_db):
        """Create FinanceModels instance with temporary database."""
        return FinanceModels(temp_db)

    def test_initialize_finance_schema(self, finance_models):
        """Test complete finance schema initialization."""
        # Initialize schema
        finance_models.initialize_finance_schema()

        # Validate schema was created
        assert finance_models.validate_schema()

        # Check all required tables exist
        schema_info = finance_models.get_schema_info()
        expected_tables = [
            'cost_categories',
            'cost_items',
            'cost_periods',
            'cost_transactions',
            'issue_cost_allocations',
            'issue_activity_log'
        ]

        for table in expected_tables:
            assert table in schema_info['tables']
            assert len(schema_info['tables'][table]['columns']) > 0

    def test_cost_categories_table(self, finance_models):
        """Test cost categories table structure and data."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Test default categories were inserted
        cursor.execute('SELECT COUNT(*) FROM cost_categories')
        count = cursor.fetchone()[0]
        assert count >= 8  # At least 8 default categories

        # Test unique constraint
        with pytest.raises(Exception):  # Should violate unique constraint
            cursor.execute('''
                INSERT INTO cost_categories (name, description)
                VALUES ('Infrastructure', 'Duplicate category')
            ''')

        conn.close()

    def test_cost_items_table(self, finance_models):
        """Test cost items table constraints and relationships."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Insert test category
        cursor.execute('''
            INSERT INTO cost_categories (name, description)
            VALUES ('Test Category', 'For testing')
        ''')
        category_id = cursor.lastrowid

        # Test valid cost item insertion
        cursor.execute('''
            INSERT INTO cost_items
            (category_id, name, cost_type, amount_eur, starting_from_date)
            VALUES (?, 'Test Server', 'monthly', 10.50, '2025-01-01')
        ''', (category_id,))

        # Test cost_type constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, cost_type, amount_eur, starting_from_date)
                VALUES (?, 'Invalid Type', 'invalid', 10.00, '2025-01-01')
            ''', (category_id,))

        # Test negative amount constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, cost_type, amount_eur, starting_from_date)
                VALUES (?, 'Negative Cost', 'monthly', -10.00, '2025-01-01')
            ''', (category_id,))

        # Test date range constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, cost_type, amount_eur, starting_from_date, ending_date)
                VALUES (?, 'Invalid Dates', 'monthly', 10.00, '2025-01-01', '2024-12-31')
            ''', (category_id,))

        conn.close()

    def test_cost_periods_table(self, finance_models):
        """Test cost periods table constraints."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Test valid period insertion
        cursor.execute('''
            INSERT INTO cost_periods (period_start, period_end)
            VALUES ('2025-01-01', '2025-01-31')
        ''')

        # Test period date constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_periods (period_start, period_end)
                VALUES ('2025-01-31', '2025-01-01')
            ''')

        # Test status constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_periods (period_start, period_end, status)
                VALUES ('2025-02-01', '2025-02-28', 'invalid_status')
            ''')

        # Test unique period constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_periods (period_start, period_end)
                VALUES ('2025-01-01', '2025-01-31')
            ''')

        conn.close()

    def test_cost_transactions_table(self, finance_models):
        """Test cost transactions table and audit trail."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Create test data
        cursor.execute('''
            INSERT INTO cost_categories (name) VALUES ('Test Category')
        ''')
        category_id = cursor.lastrowid

        cursor.execute('''
            INSERT INTO cost_items
            (category_id, name, cost_type, amount_eur, starting_from_date)
            VALUES (?, 'Test Item', 'monthly', 10.00, '2025-01-01')
        ''', (category_id,))
        cost_item_id = cursor.lastrowid

        cursor.execute('''
            INSERT INTO cost_periods (period_start, period_end)
            VALUES ('2025-01-01', '2025-01-31')
        ''')
        period_id = cursor.lastrowid

        # Test valid transaction
        cursor.execute('''
            INSERT INTO cost_transactions
            (period_id, cost_item_id, transaction_type, amount_eur, transaction_date)
            VALUES (?, ?, 'cost_incurred', 10.00, '2025-01-15')
        ''', (period_id, cost_item_id))

        # Test transaction type constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_transactions
                (period_id, cost_item_id, transaction_type, amount_eur, transaction_date)
                VALUES (?, ?, 'invalid_type', 10.00, '2025-01-15')
            ''', (period_id, cost_item_id))

        conn.close()

    def test_issue_cost_allocations_table(self, finance_models):
        """Test issue cost allocations table."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Create test period
        cursor.execute('''
            INSERT INTO cost_periods (period_start, period_end)
            VALUES ('2025-01-01', '2025-01-31')
        ''')
        period_id = cursor.lastrowid

        # Test valid allocation
        cursor.execute('''
            INSERT INTO issue_cost_allocations
            (issue_id, period_id, allocated_amount, allocation_date)
            VALUES (123, ?, 5.50, '2025-01-31')
        ''', (period_id,))

        # Test positive amount constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO issue_cost_allocations
                (issue_id, period_id, allocated_amount, allocation_date)
                VALUES (124, ?, -1.00, '2025-01-31')
            ''', (period_id,))

        # Test unique issue-period constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO issue_cost_allocations
                (issue_id, period_id, allocated_amount, allocation_date)
                VALUES (123, ?, 3.00, '2025-01-31')
            ''', (period_id,))

        conn.close()

    def test_issue_activity_log_table(self, finance_models):
        """Test issue activity log table."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Test valid activity log entry
        cursor.execute('''
            INSERT INTO issue_activity_log
            (issue_id, activity_type, activity_date)
            VALUES (123, 'created', '2025-01-15')
        ''')

        # Test activity type constraint
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO issue_activity_log
                (issue_id, activity_type, activity_date)
                VALUES (124, 'invalid_activity', '2025-01-15')
            ''')

        conn.close()

    def test_foreign_key_constraints(self, finance_models):
        """Test foreign key relationships are enforced."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Test cost_items references cost_categories
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, cost_type, amount_eur, starting_from_date)
                VALUES (999, 'Invalid Category', 'monthly', 10.00, '2025-01-01')
            ''')

        # Test cost_transactions references cost_periods
        with pytest.raises(Exception):
            cursor.execute('''
                INSERT INTO cost_transactions
                (period_id, transaction_type, amount_eur, transaction_date)
                VALUES (999, 'cost_incurred', 10.00, '2025-01-15')
            ''')

        conn.close()

    def test_indexes_created(self, finance_models):
        """Test that performance indexes are created."""
        finance_models.initialize_finance_schema()

        schema_info = finance_models.get_schema_info()
        index_names = [idx['name'] for idx in schema_info['indexes']]

        # Check critical indexes exist
        expected_indexes = [
            'idx_cost_items_active',
            'idx_cost_items_type',
            'idx_cost_periods_status',
            'idx_cost_transactions_period',
            'idx_issue_allocations_issue'
        ]

        for index in expected_indexes:
            assert index in index_names

    def test_schema_validation(self, finance_models):
        """Test schema validation functionality."""
        # Before initialization
        assert not finance_models.validate_schema()

        # After initialization
        finance_models.initialize_finance_schema()
        assert finance_models.validate_schema()

    def test_drop_finance_schema(self, finance_models):
        """Test schema cleanup functionality."""
        # Initialize schema
        finance_models.initialize_finance_schema()
        assert finance_models.validate_schema()

        # Drop schema
        finance_models.drop_finance_schema()
        assert not finance_models.validate_schema()

    def test_database_integration(self, temp_db):
        """Test integration with existing DatabaseManager."""
        from markitect.database import DatabaseManager

        # Initialize standard database
        db_manager = DatabaseManager(temp_db)
        db_manager.initialize_database()

        # Verify finance tables were also created
        finance_models = FinanceModels(temp_db)
        assert finance_models.validate_schema()

        # Verify existing tables still exist
        conn = finance_models.get_connection()
        cursor = conn.cursor()

        cursor.execute('''
            SELECT name FROM sqlite_master
            WHERE type='table' AND name IN ('markdown_files', 'schemas')
        ''')
        existing_tables = [row[0] for row in cursor.fetchall()]

        assert 'markdown_files' in existing_tables
        assert 'schemas' in existing_tables

        conn.close()

    def test_decimal_precision(self, finance_models):
        """Test decimal precision for financial calculations."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Insert test category
        cursor.execute('''
            INSERT INTO cost_categories (name) VALUES ('Test Category')
        ''')
        category_id = cursor.lastrowid

        # Test precise decimal amounts
        test_amounts = [10.50, 99.99, 0.01, 1234.56]

        for amount in test_amounts:
            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, cost_type, amount_eur, starting_from_date)
                VALUES (?, ?, 'monthly', ?, '2025-01-01')
            ''', (category_id, f'Test Item {amount}', amount))

        # Verify precision is maintained
        cursor.execute('SELECT amount_eur FROM cost_items ORDER BY id')
        stored_amounts = [float(row[0]) for row in cursor.fetchall()]

        assert stored_amounts == test_amounts

        conn.close()

    def test_example_cost_data(self, finance_models):
        """Test insertion of example cost data from issue description."""
        finance_models.initialize_finance_schema()

        conn = finance_models.get_connection()
        cursor = conn.cursor()

        # Get category IDs
        cursor.execute('SELECT id, name FROM cost_categories')
        categories = {name: id for id, name in cursor.fetchall()}

        # Insert example costs from issue #88
        example_costs = [
            ('Infrastructure', 'Hosteurope Server', 'Monthly server hosting', 10.00),
            ('Software', 'Bubble.io Plan', 'No-code platform subscription', 32.00),
            ('Domain & DNS', 'Coulomb.social Domain', 'Domain registration', 5.00),
            ('Development Tools', 'Claude Code Plan', 'AI coding assistant', 20.00),
            ('AI & ML Services', 'Gemini Plan', 'LLM API for specifications', 20.00)
        ]

        for category_name, name, description, amount in example_costs:
            category_id = categories.get(category_name)
            assert category_id is not None

            cursor.execute('''
                INSERT INTO cost_items
                (category_id, name, description, cost_type, amount_eur, starting_from_date)
                VALUES (?, ?, ?, 'monthly', ?, '2025-01-01')
            ''', (category_id, name, description, amount))

        # Verify total monthly costs
        cursor.execute('''
            SELECT SUM(amount_eur) FROM cost_items
            WHERE cost_type = 'monthly' AND is_active = TRUE
        ''')
        total_monthly = float(cursor.fetchone()[0])

        assert total_monthly == 87.00  # €87/month as described in issue

        conn.close()