"""
Tests for MarkiTect period management CLI commands.

This module tests the command-line interface for period management including:
- Period creation, listing, and status management
- Period calculation and lifecycle operations
- CLI error handling and validation
"""

import pytest
import tempfile
import os
from datetime import date
from decimal import Decimal
from click.testing import CliRunner

from markitect.finance.cli import cost_commands
from markitect.finance.models import FinanceModels
from markitect.finance.period_manager import PeriodManager
from markitect.finance.cost_manager import CostItemManager, CostItem


class TestPeriodCLICommands:
    """Test suite for period management CLI commands."""

    @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 setup_test_data(self, temp_db):
        """Setup test database with sample period data."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        period_manager = PeriodManager(temp_db)

        # Create sample period
        period_id = period_manager.create_period(
            period_start=date(2025, 1, 1),
            period_end=date(2025, 1, 31),
            period_type='monthly'
        )

        return temp_db, period_id

    @pytest.fixture
    def runner(self):
        """Create Click test runner."""
        return CliRunner()

    def test_period_create_success(self, runner, temp_db):
        """Test period creation via CLI."""
        # Initialize database first
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'create',
            '--start-date', '2025-02-01',
            '--end-date', '2025-02-28',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "✅ Created period #" in result.output
        assert "📅 Period: 2025-02-01 to 2025-02-28" in result.output
        assert "📊 Type: monthly" in result.output

    def test_period_create_with_loss_forward(self, runner, temp_db):
        """Test period creation with loss carried forward."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'create',
            '--start-date', '2025-03-01',
            '--end-date', '2025-03-31',
            '--loss-forward', '15.75',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "✅ Created period #" in result.output
        assert "💸 Loss carried forward: €15.7500" in result.output

    def test_period_create_invalid_dates(self, runner, temp_db):
        """Test period creation with invalid date format."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'create',
            '--start-date', 'invalid-date',
            '--end-date', '2025-02-28',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error: Dates must be in YYYY-MM-DD format" in result.output

    def test_period_create_overlapping_fails(self, runner, setup_test_data):
        """Test that creating overlapping periods fails."""
        temp_db, existing_period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'create',
            '--start-date', '2025-01-15',  # Overlaps with existing period
            '--end-date', '2025-02-15',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error:" in result.output
        assert "overlaps" in result.output.lower()

    def test_period_list_all(self, runner, setup_test_data):
        """Test listing all periods."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'list',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "📅 Calculation Periods" in result.output
        assert "2025-01-01" in result.output
        assert "2025-01-31" in result.output
        assert "Total: 1 periods" in result.output

    def test_period_list_with_status_filter(self, runner, setup_test_data):
        """Test listing periods with status filter."""
        temp_db, period_id = setup_test_data

        # Create second period and close it
        period_manager = PeriodManager(temp_db)
        period_id2 = period_manager.create_period(
            period_start=date(2025, 2, 1),
            period_end=date(2025, 2, 28)
        )
        period_manager.close_period(period_id2)

        # Filter by open status
        result = runner.invoke(cost_commands, [
            'period', 'list',
            '--status', 'open',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "2025-01-01" in result.output  # First period should be shown
        assert "2025-02-01" not in result.output  # Second period should be filtered out

        # Filter by closed status
        result = runner.invoke(cost_commands, [
            'period', 'list',
            '--status', 'closed',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "2025-02-01" in result.output  # Second period should be shown
        assert "2025-01-01" not in result.output  # First period should be filtered out

    def test_period_list_with_date_filters(self, runner, temp_db):
        """Test listing periods with date range filters."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        period_manager = PeriodManager(temp_db)

        # Create periods in different months
        jan_period = period_manager.create_period(date(2025, 1, 1), date(2025, 1, 31))
        feb_period = period_manager.create_period(date(2025, 2, 1), date(2025, 2, 28))

        # Filter by start date
        result = runner.invoke(cost_commands, [
            'period', 'list',
            '--start-from', '2025-02-01',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "2025-02-01" in result.output
        assert "2025-01-01" not in result.output

    def test_period_list_empty(self, runner, temp_db):
        """Test listing periods when none exist."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'list',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "No periods found matching criteria" in result.output

    def test_period_show_details(self, runner, setup_test_data):
        """Test showing period details."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'show', str(period_id),
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert f"📅 Period #{period_id} Details" in result.output
        assert "Start Date: 2025-01-01" in result.output
        assert "End Date: 2025-01-31" in result.output
        assert "Type: monthly" in result.output
        assert "Status: open" in result.output

    def test_period_show_nonexistent(self, runner, temp_db):
        """Test showing non-existent period."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'show', '999',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Period #999 not found" in result.output

    def test_period_calculate(self, runner, setup_test_data):
        """Test period cost calculation."""
        temp_db, period_id = setup_test_data

        # Add some cost items for calculation
        cost_manager = CostItemManager(temp_db)
        infra_cat = cost_manager.get_category_by_name('Infrastructure')

        cost_item = CostItem(
            category_id=infra_cat['id'],
            name='Test Server',
            cost_type='monthly',
            amount_eur=Decimal('25.00'),
            starting_from_date=date(2025, 1, 1)
        )
        cost_manager.create_cost_item(cost_item)

        result = runner.invoke(cost_commands, [
            'period', 'calculate', str(period_id),
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert f"📊 Period #{period_id} Cost Calculation" in result.output
        assert "Period: 2025-01-01 to 2025-01-31" in result.output
        assert "Monthly Recurring: €25.00" in result.output
        assert "Total Period Cost: €25.00" in result.output

    def test_period_calculate_nonexistent(self, runner, temp_db):
        """Test calculating costs for non-existent period."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'calculate', '999',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error calculating period:" in result.output

    def test_period_status_update(self, runner, setup_test_data):
        """Test period status update."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'status', str(period_id), 'calculating',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert f"✅ Period #{period_id} status updated to 'calculating'" in result.output

        # Verify the status was actually updated
        result = runner.invoke(cost_commands, [
            'period', 'show', str(period_id),
            '--database', temp_db
        ])

        assert "Status: calculating" in result.output

    def test_period_status_update_invalid_status(self, runner, setup_test_data):
        """Test period status update with invalid status."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'status', str(period_id), 'invalid',
            '--database', temp_db
        ])

        assert result.exit_code == 2  # Click validation error
        assert "Invalid value" in result.output

    def test_period_status_update_nonexistent(self, runner, temp_db):
        """Test status update for non-existent period."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'status', '999', 'calculating',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error:" in result.output

    def test_period_close(self, runner, setup_test_data):
        """Test period closure."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'close', str(period_id),
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert f"✅ Period #{period_id} has been closed" in result.output
        assert "💰 Final total cost:" in result.output

        # Verify the period is actually closed
        result = runner.invoke(cost_commands, [
            'period', 'show', str(period_id),
            '--database', temp_db
        ])

        assert "Status: closed" in result.output

    def test_period_close_nonexistent(self, runner, temp_db):
        """Test closing non-existent period."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'close', '999',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error:" in result.output

    def test_period_current_exists(self, runner, setup_test_data):
        """Test finding current period when it exists."""
        temp_db, period_id = setup_test_data

        result = runner.invoke(cost_commands, [
            'period', 'current',
            '--date', '2025-01-15',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "📅 Current Active Period" in result.output
        assert f"Period #{period_id}" in result.output
        assert "Dates: 2025-01-01 to 2025-01-31" in result.output

    def test_period_current_not_found(self, runner, temp_db):
        """Test finding current period when none exists."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'current',
            '--date', '2025-03-15',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "No active period found for 2025-03-15" in result.output

    def test_period_current_default_to_today(self, runner, temp_db):
        """Test current period defaults to today."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'current',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "No active period found for today" in result.output
        assert "💡 Create one with:" in result.output
        assert "markitect cost period create" in result.output

    def test_period_current_invalid_date(self, runner, temp_db):
        """Test current period with invalid date format."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'current',
            '--date', 'invalid-date',
            '--database', temp_db
        ])

        assert result.exit_code == 1
        assert "Error: Date must be in YYYY-MM-DD format" in result.output

    def test_period_help_commands(self, runner):
        """Test help output for period commands."""
        # Test main period help
        result = runner.invoke(cost_commands, ['period', '--help'])
        assert result.exit_code == 0
        assert "Manage calculation periods and lifecycle" in result.output

        # Test create help
        result = runner.invoke(cost_commands, ['period', 'create', '--help'])
        assert result.exit_code == 0
        assert "Create a new calculation period" in result.output

        # Test list help
        result = runner.invoke(cost_commands, ['period', 'list', '--help'])
        assert result.exit_code == 0
        assert "List calculation periods with optional filtering" in result.output

    def test_period_commands_missing_database(self, runner):
        """Test period commands without database specification."""
        # These should use default config path and still work or show appropriate error
        result = runner.invoke(cost_commands, [
            'period', 'list'
        ])

        # Should succeed with default database configuration
        assert result.exit_code == 0

    def test_period_create_quarterly_type(self, runner, temp_db):
        """Test creating quarterly period type."""
        finance_models = FinanceModels(temp_db)
        finance_models.initialize_finance_schema()

        result = runner.invoke(cost_commands, [
            'period', 'create',
            '--start-date', '2025-04-01',
            '--end-date', '2025-06-30',
            '--type', 'quarterly',
            '--database', temp_db
        ])

        assert result.exit_code == 0
        assert "✅ Created period #" in result.output
        assert "📊 Type: quarterly" in result.output