import pytest
import asyncio
from fastapi.testclient import TestClient
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
import json
import time
import random
import string

from app.main import app
from app.core.database import get_db, Base
from app.models.trip import Trip
from app.models.participant import Participant
from app.models.expense import Expense, ExpenseSplit

# Test database setup
SQLALCHEMY_DATABASE_URL = "sqlite:///./test.db"
engine = create_engine(SQLALCHEMY_DATABASE_URL, connect_args={"check_same_thread": False})
TestingSessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

def override_get_db():
    try:
        db = TestingSessionLocal()
        yield db
    finally:
        db.close()

app.dependency_overrides[get_db] = override_get_db

# Create test client
client = TestClient(app)

def setup_database():
    """Setup test database"""
    Base.metadata.create_all(bind=engine)

def cleanup_database():
    """Clean up test database"""
    Base.metadata.drop_all(bind=engine)

def generate_share_code():
    """Generate a random 6-character share code"""
    return ''.join(random.choices(string.ascii_uppercase + string.digits, k=6))

def create_test_trip():
    """Helper function to create a test trip"""
    trip_data = {
        "name": "Test Trip Paris",
        "creator_name": "John Doe",
        "currency_code": "USD"
    }
    response = client.post("/api/trips/", json=trip_data)
    return response.json()

def create_test_participant(trip_id, name="Test Participant"):
    """Helper function to create a test participant"""
    participant_data = {"name": name}
    response = client.post(f"/api/participants/{trip_id}/participants", json=participant_data)
    return response.json()

class TestExpenseAPI:
    """Test suite for Expense API endpoints"""

    @pytest.fixture(autouse=True)
    def setup(self):
        """Setup for each test"""
        setup_database()
        yield
        cleanup_database()

    def test_create_expense(self):
        """Test creating a new expense"""
        # Create trip and participants
        trip = create_test_trip()
        participant1 = create_test_participant(trip["id"], "Alice")
        participant2 = create_test_participant(trip["id"], "Bob")

        # Create expense data
        expense_data = {
            "description": "Dinner at Restaurant",
            "amount": 100.50,
            "currency_code": "USD",
            "exchange_rate": 1.0,
            "category": "Food & Dining",
            "paid_by_id": participant1["id"],
            "splits": [
                {"participant_id": participant1["id"], "percentage": 50.0},
                {"participant_id": participant2["id"], "percentage": 50.0}
            ]
        }

        # Create expense
        response = client.post(f"/api/expenses/{trip['id']}/expenses", json=expense_data)
        assert response.status_code == 200

        expense = response.json()
        assert expense["description"] == "Dinner at Restaurant"
        assert expense["amount"] == 100.50
        assert expense["currency_code"] == "USD"
        assert expense["category"] == "Food & Dining"
        assert expense["paid_by_id"] == participant1["id"]
        assert len(expense["splits"]) == 2
        assert expense["amount_in_trip_currency"] == 100.50

        # Check splits
        splits = expense["splits"]
        alice_split = next(s for s in splits if s["participant_id"] == participant1["id"])
        bob_split = next(s for s in splits if s["participant_id"] == participant2["id"])

        assert alice_split["percentage"] == 50.0
        assert alice_split["amount"] == 50.25
        assert bob_split["percentage"] == 50.0
        assert bob_split["amount"] == 50.25

    def test_create_expense_with_currency_conversion(self):
        """Test creating an expense with currency conversion"""
        # Create trip with EUR currency
        trip_data = {
            "name": "Europe Trip",
            "creator_name": "Alice",
            "currency_code": "EUR"
        }
        trip_response = client.post("/api/trips/", json=trip_data)
        trip = trip_response.json()

        participant1 = create_test_participant(trip["id"], "Alice")
        participant2 = create_test_participant(trip["id"], "Bob")

        # Create expense in USD
        expense_data = {
            "description": "Hotel Booking",
            "amount": 120.00,
            "currency_code": "USD",
            "exchange_rate": 0.85,  # 1 USD = 0.85 EUR
            "category": "Accommodation",
            "paid_by_id": participant1["id"],
            "splits": [
                {"participant_id": participant1["id"], "percentage": 100.0}
            ]
        }

        response = client.post(f"/api/expenses/{trip['id']}/expenses", json=expense_data)
        assert response.status_code == 200

        expense = response.json()
        assert expense["amount"] == 120.00
        assert expense["currency_code"] == "USD"
        assert expense["amount_in_trip_currency"] == 102.00  # 120 * 0.85

    def test_get_expenses(self):
        """Test retrieving expenses for a trip"""
        # Create trip and participants
        trip = create_test_trip()
        participant1 = create_test_participant(trip["id"], "Alice")
        participant2 = create_test_participant(trip["id"], "Bob")

        # Create multiple expenses
        expenses = [
            {
                "description": "Lunch",
                "amount": 25.00,
                "currency_code": "USD",
                "exchange_rate": 1.0,
                "category": "Food & Dining",
                "paid_by_id": participant1["id"],
                "splits": [
                    {"participant_id": participant1["id"], "percentage": 50.0},
                    {"participant_id": participant2["id"], "percentage": 50.0}
                ]
            },
            {
                "description": "Taxi",
                "amount": 15.00,
                "currency_code": "USD",
                "exchange_rate": 1.0,
                "category": "Transportation",
                "paid_by_id": participant2["id"],
                "splits": [
                    {"participant_id": participant1["id"], "percentage": 100.0}
                ]
            }
        ]

        for expense_data in expenses:
            client.post(f"/api/expenses/{trip['id']}/expenses", json=expense_data)

        # Get expenses
        response = client.get(f"/api/expenses/{trip['id']}/expenses")
        assert response.status_code == 200

        retrieved_expenses = response.json()
        assert len(retrieved_expenses) == 2

        # Check expense details
        expense_descriptions = [e["description"] for e in retrieved_expenses]
        assert "Lunch" in expense_descriptions
        assert "Taxi" in expense_descriptions

    def test_expense_validation(self):
        """Test expense validation"""
        trip = create_test_trip()
        participant = create_test_participant(trip["id"], "Alice")

        # Test invalid expense (splits don't total 100%)
        invalid_expense = {
            "description": "Invalid Expense",
            "amount": 100.00,
            "currency_code": "USD",
            "exchange_rate": 1.0,
            "category": "Other",
            "paid_by_id": participant["id"],
            "splits": [
                {"participant_id": participant["id"], "percentage": 80.0}  # Only 80%
            ]
        }

        response = client.post(f"/api/expenses/{trip['id']}/expenses", json=invalid_expense)
        assert response.status_code == 400
        assert "Splits must total 100%" in response.json()["detail"]

        # Test negative amount
        invalid_expense = {
            "description": "Negative Amount",
            "amount": -50.00,
            "currency_code": "USD",
            "exchange_rate": 1.0,
            "category": "Other",
            "paid_by_id": participant["id"],
            "splits": [
                {"participant_id": participant["id"], "percentage": 100.0}
            ]
        }

        response = client.post(f"/api/expenses/{trip['id']}/expenses", json=invalid_expense)
        assert response.status_code == 422

    def test_expense_categories(self):
        """Test expense with different categories"""
        trip = create_test_trip()
        participant = create_test_participant(trip["id"], "Alice")

        categories = [
            "Food & Dining",
            "Transportation",
            "Accommodation",
            "Entertainment",
            "Shopping",
            "Healthcare",
            "Education",
            "Business",
            "Other"
        ]

        for category in categories:
            expense_data = {
                "description": f"Test {category} expense",
                "amount": 50.00,
                "currency_code": "USD",
                "exchange_rate": 1.0,
                "category": category,
                "paid_by_id": participant["id"],
                "splits": [
                    {"participant_id": participant["id"], "percentage": 100.0}
                ]
            }

            response = client.post(f"/api/expenses/{trip['id']}/expenses", json=expense_data)
            assert response.status_code == 200

            expense = response.json()
            assert expense["category"] == category

    def test_get_trip_summary(self):
        """Test retrieving trip summary with expenses"""
        # Create trip and participants
        trip = create_test_trip()
        participant1 = create_test_participant(trip["id"], "Alice")
        participant2 = create_test_participant(trip["id"], "Bob")

        # Create expenses
        expenses = [
            {
                "description": "Restaurant (Alice paid, split equally)",
                "amount": 100.00,
                "currency_code": "USD",
                "exchange_rate": 1.0,
                "category": "Food & Dining",
                "paid_by_id": participant1["id"],
                "splits": [
                    {"participant_id": participant1["id"], "percentage": 50.0},
                    {"participant_id": participant2["id"], "percentage": 50.0}
                ]
            },
            {
                "description": "Taxi (Bob paid, Alice uses)",
                "amount": 40.00,
                "currency_code": "USD",
                "exchange_rate": 1.0,
                "category": "Transportation",
                "paid_by_id": participant2["id"],
                "splits": [
                    {"participant_id": participant1["id"], "percentage": 100.0}
                ]
            }
        ]

        for expense_data in expenses:
            client.post(f"/api/expenses/{trip['id']}/expenses", json=expense_data)

        # Get trip summary
        response = client.get(f"/api/trips/{trip['id']}")
        assert response.status_code == 200

        trip_data = response.json()
        assert trip_data["total_expenses"] == 140.00
        assert trip_data["currency_code"] == "USD"

if __name__ == "__main__":
    pytest.main([__file__, "-v"])