"""Tests for the refactored FastAPI Monitor components."""

import pytest
from fastapi import FastAPI
from fastapi.testclient import TestClient

from fastapi_monitor.config import ColorScheme, MonitorConfig
from fastapi_monitor.exceptions import ValidationError
from fastapi_monitor.models import ErrorInfo, RequestData, ResponseData
from fastapi_monitor.repository import SQLiteRepository
from fastapi_monitor.services import MonitorService, RequestProcessor


class TestMonitorConfig:
    """Test configuration management."""

    def test_default_config(self):
        """Test default configuration values."""
        config = MonitorConfig()

        assert config.db_path == "monitor.db"
        assert config.exclude_paths == ["/monitor", "/health", "/metrics"]
        assert config.color_scheme == ColorScheme.DEFAULT
        assert config.max_request_body_size == 1024 * 1024
        assert config.cleanup_interval == 3600

    def test_custom_config(self):
        """Test custom configuration."""
        config = MonitorConfig(
            db_path="custom.db",
            exclude_paths=["/custom"],
            color_scheme=ColorScheme.WARM,
            dashboard_username="admin",
            dashboard_password="secret"
        )

        assert config.db_path == "custom.db"
        assert config.exclude_paths == ["/custom"]
        assert config.color_scheme == ColorScheme.WARM
        assert config.dashboard_username == "admin"
        assert config.dashboard_password == "secret"

    def test_validation(self):
        """Test configuration validation."""
        # Test invalid page size would be caught by pydantic
        config = MonitorConfig(max_records=500)  # Below minimum
        assert config.max_records == 500  # Pydantic allows this, validation in service


class TestModels:
    """Test data models."""

    def test_request_data(self):
        """Test RequestData model."""
        data = RequestData(
            method="GET",
            path="/test",
            timestamp=1234567890.0
        )

        assert data.method == "GET"
        assert data.path == "/test"
        assert data.timestamp == 1234567890.0
        assert data.headers == {}
        assert data.request_size == 0

    def test_request_data_validation(self):
        """Test RequestData validation."""
        # Test method validation
        data = RequestData(
            method="get",  # lowercase
            path="test",   # no leading slash
            timestamp=1234567890.0
        )

        assert data.method == "GET"  # Should be uppercase
        assert data.path == "/test"  # Should add leading slash

    def test_response_data(self):
        """Test ResponseData model."""
        data = ResponseData(
            status_code=200,
            response_time=123.45
        )

        assert data.status_code == 200
        assert data.response_time == 123.45
        assert data.response_size == 0
        assert data.response_headers == {}

    def test_error_info(self):
        """Test ErrorInfo model."""
        error = ErrorInfo(
            error_type="ValueError",
            error_message="Test error",
            traceback="Traceback..."
        )

        assert error.error_type == "ValueError"
        assert error.error_message == "Test error"
        assert error.traceback == "Traceback..."

    def test_monitor_record(self):
        """Test MonitorRecord model."""
        from fastapi_monitor.models import MonitorRecord

        request_data = RequestData(
            method="GET",
            path="/test",
            timestamp=1234567890.0
        )

        response_data = ResponseData(
            status_code=200,
            response_time=123.45
        )

        record = MonitorRecord(
            request=request_data,
            response=response_data
        )

        assert record.request == request_data
        assert record.response == response_data
        assert record.error_info is None
        assert not record.is_error

    def test_monitor_record_with_error(self):
        """Test MonitorRecord with error."""
        from fastapi_monitor.models import MonitorRecord

        request_data = RequestData(
            method="GET",
            path="/test",
            timestamp=1234567890.0
        )

        response_data = ResponseData(
            status_code=500,
            response_time=123.45
        )

        error_info = ErrorInfo(
            error_type="ValueError",
            error_message="Test error"
        )

        record = MonitorRecord(
            request=request_data,
            response=response_data,
            error_info=error_info
        )

        assert record.is_error  # Should be True due to error_info


class TestRequestProcessor:
    """Test request processor."""

    def test_extract_request_data(self):
        """Test request data extraction."""
        config = MonitorConfig()
        processor = RequestProcessor(config)

        # Mock request object
        class MockRequest:
            method = "GET"
            url = type('URL', (), {'path': '/test', 'query': 'param=value'})()
            headers = {'user-agent': 'test-agent', 'content-length': '100'}
            client = type('Client', (), {'host': '127.0.0.1'})()

        request = MockRequest()
        start_time = 1234567890.0

        data = processor.extract_request_data(request, start_time, "test body")

        assert data.method == "GET"
        assert data.path == "/test"
        assert data.query_params == "param=value"
        assert data.client_ip == "127.0.0.1"
        assert data.user_agent == "test-agent"
        assert data.request_body == "test body"
        assert data.timestamp == start_time

    def test_extract_response_data(self):
        """Test response data extraction."""
        config = MonitorConfig()
        processor = RequestProcessor(config)

        # Mock response object
        class MockResponse:
            status_code = 200
            headers = {'content-type': 'application/json'}

        response = MockResponse()
        response_time = 123.45

        data = processor.extract_response_data(response, response_time, "response body")

        assert data.status_code == 200
        assert data.response_time == 123.45
        assert data.response_body == "response body"
        assert data.response_headers == {'content-type': 'application/json'}

    def test_extract_error_info(self):
        """Test error info extraction."""
        config = MonitorConfig()
        processor = RequestProcessor(config)

        try:
            raise ValueError("Test error")
        except Exception as e:
            error_info = processor.extract_error_info(e)

        assert error_info.error_type == "ValueError"
        assert error_info.error_message == "Test error"
        assert "ValueError: Test error" in error_info.traceback


@pytest.mark.asyncio
class TestSQLiteRepository:
    """Test SQLite repository."""

    async def test_store_and_retrieve_record(self, tmp_path):
        """Test storing and retrieving records."""
        config = MonitorConfig(db_path=str(tmp_path / "test.db"))
        repo = SQLiteRepository(config)

        # Create test record
        request_data = RequestData(
            method="GET",
            path="/test",
            timestamp=1234567890.0
        )

        response_data = ResponseData(
            status_code=200,
            response_time=123.45
        )

        from fastapi_monitor.models import MonitorRecord
        record = MonitorRecord(
            request=request_data,
            response=response_data
        )

        # Store record
        await repo.store_record(record)

        # Retrieve records
        records = await repo.get_records(10, 0)
        assert len(records) == 1

        retrieved = records[0]
        assert retrieved.request.method == "GET"
        assert retrieved.request.path == "/test"
        assert retrieved.response.status_code == 200

    async def test_get_stats(self, tmp_path):
        """Test getting statistics."""
        config = MonitorConfig(db_path=str(tmp_path / "test.db"))
        repo = SQLiteRepository(config)

        # Store some test records
        for i in range(5):
            request_data = RequestData(
                method="GET",
                path=f"/test{i}",
                timestamp=1234567890.0 + i
            )

            response_data = ResponseData(
                status_code=200 if i < 3 else 500,
                response_time=100.0 + i * 10
            )

            from fastapi_monitor.models import MonitorRecord
            record = MonitorRecord(
                request=request_data,
                response=response_data
            )

            await repo.store_record(record)

        # Get stats
        stats = await repo.get_stats()

        assert stats.total_requests == 5
        assert stats.error_count == 2  # 2 records with status >= 400
        assert stats.avg_response_time > 0


@pytest.mark.asyncio
class TestMonitorService:
    """Test monitor service."""

    async def test_record_request(self, tmp_path):
        """Test recording requests."""
        config = MonitorConfig(db_path=str(tmp_path / "test.db"))
        repo = SQLiteRepository(config)
        service = MonitorService(repo, config)

        request_data = RequestData(
            method="GET",
            path="/test",
            timestamp=1234567890.0
        )

        response_data = ResponseData(
            status_code=200,
            response_time=123.45
        )

        await service.record_request(request_data, response_data)

        # Verify it was stored
        records, _ = await service.get_recent_requests()
        assert len(records) == 1

    async def test_get_recent_requests_pagination(self, tmp_path):
        """Test pagination in get_recent_requests."""
        config = MonitorConfig(db_path=str(tmp_path / "test.db"))
        repo = SQLiteRepository(config)
        service = MonitorService(repo, config)

        # Store multiple records
        for i in range(25):
            request_data = RequestData(
                method="GET",
                path=f"/test{i}",
                timestamp=1234567890.0 + i
            )

            response_data = ResponseData(
                status_code=200,
                response_time=100.0
            )

            await service.record_request(request_data, response_data)

        # Test pagination
        records, pagination = await service.get_recent_requests(page=1, page_size=10)

        assert len(records) == 10
        assert pagination.current_page == 1
        assert pagination.total_pages == 3
        assert pagination.total_records == 25
        assert pagination.has_next
        assert not pagination.has_previous

        # Test second page
        records, pagination = await service.get_recent_requests(page=2, page_size=10)

        assert len(records) == 10
        assert pagination.current_page == 2
        assert pagination.has_next
        assert pagination.has_previous

    async def test_validation_errors(self, tmp_path):
        """Test validation errors."""
        config = MonitorConfig(db_path=str(tmp_path / "test.db"))
        repo = SQLiteRepository(config)
        service = MonitorService(repo, config)

        # Test invalid page number
        with pytest.raises(ValidationError):
            await service.get_recent_requests(page=0)

        # Test invalid page size
        with pytest.raises(ValidationError):
            await service.get_recent_requests(page_size=0)

        with pytest.raises(ValidationError):
            await service.get_recent_requests(page_size=200)

        # Test invalid request ID
        with pytest.raises(ValidationError):
            await service.get_request_details(0)

    def test_should_monitor_path(self, tmp_path):
        """Test path monitoring logic."""
        config = MonitorConfig(
            db_path=str(tmp_path / "test.db"),
            exclude_paths=["/monitor", "/health"]
        )
        repo = SQLiteRepository(config)
        service = MonitorService(repo, config)

        assert service.should_monitor_path("/api/users")
        assert not service.should_monitor_path("/monitor")
        assert not service.should_monitor_path("/monitor/dashboard")
        assert not service.should_monitor_path("/health")
        assert service.should_monitor_path("/healthcheck")  # Different path


class TestIntegration:
    """Integration tests."""

    def test_quick_setup(self):
        """Test quick setup function."""
        from fastapi_monitor import quick_setup

        app = FastAPI()

        # This should not raise any errors
        quick_setup(
            app,
            db_path=":memory:",  # Use in-memory database for testing
            username="test",
            password="test"
        )

        # Verify middleware was added
        assert len(app.user_middleware) > 0

        # Verify dashboard was mounted
        assert any("/monitor" in str(route) for route in app.routes)

    def test_dashboard_creation(self):
        """Test dashboard creation."""
        from fastapi_monitor import MonitorConfig, create_dashboard_app

        config = MonitorConfig(
            db_path=":memory:",
            dashboard_username="test",
            dashboard_password="test"
        )

        dashboard_app = create_dashboard_app(config=config)

        assert dashboard_app.title == "FastAPI Monitor Dashboard"

        # Test with client
        client = TestClient(dashboard_app)

        # Should require authentication
        response = client.get("/")
        assert response.status_code == 401

        # Should work with authentication
        response = client.get("/", auth=("test", "test"))
        assert response.status_code == 200
