"""Repository pattern for data access."""

import json
import logging
from abc import ABC, abstractmethod
from contextlib import asynccontextmanager
from datetime import datetime
from typing import AsyncGenerator, List, Optional

import aiosqlite

from .config import MonitorConfig
from .exceptions import ConnectionError, DatabaseError, StorageError
from .models import (
    AnalyticsData,
    ErrorInfo,
    MonitorRecord,
    RequestData,
    ResponseData,
    StatsData,
)

logger = logging.getLogger(__name__)


class MonitorRepository(ABC):
    """Abstract repository interface."""

    @abstractmethod
    async def store_record(self, record: MonitorRecord) -> None:
        """Store a monitoring record."""
        pass

    @abstractmethod
    async def get_records(self, limit: int, offset: int) -> List[MonitorRecord]:
        """Get records with pagination."""
        pass

    @abstractmethod
    async def get_record_by_id(self, record_id: int) -> Optional[MonitorRecord]:
        """Get a specific record by ID."""
        pass

    @abstractmethod
    async def get_stats(self) -> StatsData:
        """Get monitoring statistics."""
        pass

    @abstractmethod
    async def get_analytics(self, resolution: str) -> AnalyticsData:
        """Get analytics data."""
        pass

    @abstractmethod
    async def cleanup_old_records(self, max_records: int) -> int:
        """Clean up old records, return number of deleted records."""
        pass


class SQLiteRepository(MonitorRepository):
    """SQLite implementation of the repository."""

    def __init__(self, config: MonitorConfig):
        self.config = config
        self._initialized = False

    @asynccontextmanager
    async def get_connection(self) -> AsyncGenerator[aiosqlite.Connection, None]:
        """Get database connection with proper error handling."""
        try:
            async with aiosqlite.connect(
                self.config.db_path,
                timeout=self.config.request_timeout
            ) as conn:
                conn.row_factory = aiosqlite.Row
                yield conn
        except Exception as e:
            logger.error(f"Database connection error: {e}")
            raise ConnectionError(f"Failed to connect to database: {e}") from e

    async def _ensure_initialized(self) -> None:
        """Ensure database is initialized."""
        if self._initialized:
            return

        try:
            async with self.get_connection() as conn:
                await self._create_tables(conn)
                await self._create_indexes(conn)
                await conn.commit()
                self._initialized = True
                logger.info("Database initialized successfully")
        except Exception as e:
            logger.error(f"Database initialization failed: {e}")
            raise DatabaseError(f"Failed to initialize database: {e}") from e

    async def _create_tables(self, conn: aiosqlite.Connection) -> None:
        """Create database tables."""
        await conn.execute("""
            CREATE TABLE IF NOT EXISTS requests (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                timestamp REAL NOT NULL,
                method TEXT NOT NULL,
                path TEXT NOT NULL,
                query_params TEXT,
                status_code INTEGER NOT NULL,
                response_time REAL NOT NULL,
                request_size INTEGER DEFAULT 0,
                response_size INTEGER DEFAULT 0,
                client_ip TEXT,
                user_agent TEXT,
                headers TEXT,
                request_body TEXT,
                response_body TEXT,
                response_headers TEXT,
                error_info TEXT,
                created_at DATETIME DEFAULT CURRENT_TIMESTAMP
            )
        """)

    async def _create_indexes(self, conn: aiosqlite.Connection) -> None:
        """Create database indexes for performance."""
        indexes = [
            "CREATE INDEX IF NOT EXISTS idx_timestamp ON requests(timestamp)",
            "CREATE INDEX IF NOT EXISTS idx_path ON requests(path)",
            "CREATE INDEX IF NOT EXISTS idx_status ON requests(status_code)",
            "CREATE INDEX IF NOT EXISTS idx_created_at ON requests(created_at)",
            "CREATE INDEX IF NOT EXISTS idx_method ON requests(method)",
        ]

        for index_sql in indexes:
            await conn.execute(index_sql)

    async def store_record(self, record: MonitorRecord) -> None:
        """Store a monitoring record."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as conn:
                await conn.execute("""
                    INSERT INTO requests (
                        timestamp, method, path, query_params, status_code,
                        response_time, request_size, response_size, client_ip,
                        user_agent, headers, request_body, response_body,
                        response_headers, error_info
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """, (
                    record.request.timestamp,
                    record.request.method,
                    record.request.path,
                    record.request.query_params,
                    record.response.status_code,
                    record.response.response_time,
                    record.request.request_size,
                    record.response.response_size,
                    record.request.client_ip,
                    record.request.user_agent,
                    json.dumps(record.request.headers) if record.request.headers else None,
                    record.request.request_body,
                    record.response.response_body,
                    json.dumps(record.response.response_headers) if record.response.response_headers else None,
                    json.dumps(record.error_info.dict()) if record.error_info else None,
                ))
                await conn.commit()
        except Exception as e:
            logger.error(f"Failed to store record: {e}")
            raise StorageError(f"Failed to store monitoring record: {e}") from e

    async def get_records(self, limit: int, offset: int) -> List[MonitorRecord]:
        """Get records with pagination."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as conn:
                async with conn.execute("""
                    SELECT * FROM requests
                    ORDER BY timestamp DESC
                    LIMIT ? OFFSET ?
                """, (limit, offset)) as cursor:
                    rows = await cursor.fetchall()
                    return [self._row_to_record(row) for row in rows]
        except Exception as e:
            logger.error(f"Failed to get records: {e}")
            raise DatabaseError(f"Failed to retrieve records: {e}") from e

    async def get_record_by_id(self, record_id: int) -> Optional[MonitorRecord]:
        """Get a specific record by ID."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as conn:
                async with conn.execute(
                    "SELECT * FROM requests WHERE id = ?", (record_id,)
                ) as cursor:
                    row = await cursor.fetchone()
                    return self._row_to_record(row) if row else None
        except Exception as e:
            logger.error(f"Failed to get record {record_id}: {e}")
            raise DatabaseError(f"Failed to retrieve record: {e}") from e

    async def get_stats(self) -> StatsData:
        """Get monitoring statistics."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as conn:
                # Total requests
                async with conn.execute("SELECT COUNT(*) FROM requests") as cursor:
                    total_requests = (await cursor.fetchone())[0]

                # Average response time
                async with conn.execute("SELECT AVG(response_time) FROM requests") as cursor:
                    avg_response_time = (await cursor.fetchone())[0] or 0.0

                # Status code distribution
                async with conn.execute("""
                    SELECT status_code, COUNT(*)
                    FROM requests
                    GROUP BY status_code
                """) as cursor:
                    status_codes = {str(row[0]): row[1] for row in await cursor.fetchall()}

                # Error count
                async with conn.execute("""
                    SELECT COUNT(*) FROM requests
                    WHERE error_info IS NOT NULL OR status_code >= 400
                """) as cursor:
                    error_count = (await cursor.fetchone())[0]

                return StatsData(
                    total_requests=total_requests,
                    avg_response_time=round(avg_response_time, 2),
                    status_codes=status_codes,
                    error_count=error_count
                )
        except Exception as e:
            logger.error(f"Failed to get stats: {e}")
            raise DatabaseError(f"Failed to retrieve statistics: {e}") from e

    async def get_analytics(self, resolution: str = "30s") -> AnalyticsData:
        """Get analytics data."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as _conn:
                # This is a simplified version - full implementation would be more complex
                return AnalyticsData()
        except Exception as e:
            logger.error(f"Failed to get analytics: {e}")
            raise DatabaseError(f"Failed to retrieve analytics: {e}") from e

    async def cleanup_old_records(self, max_records: int) -> int:
        """Clean up old records."""
        await self._ensure_initialized()

        try:
            async with self.get_connection() as conn:
                # Get current count
                async with conn.execute("SELECT COUNT(*) FROM requests") as cursor:
                    current_count = (await cursor.fetchone())[0]

                if current_count <= max_records:
                    return 0

                # Delete oldest records
                records_to_delete = current_count - max_records
                await conn.execute("""
                    DELETE FROM requests
                    WHERE id IN (
                        SELECT id FROM requests
                        ORDER BY timestamp ASC
                        LIMIT ?
                    )
                """, (records_to_delete,))

                await conn.commit()
                return records_to_delete
        except Exception as e:
            logger.error(f"Failed to cleanup records: {e}")
            raise DatabaseError(f"Failed to cleanup old records: {e}") from e

    def _row_to_record(self, row: aiosqlite.Row) -> MonitorRecord:
        """Convert database row to MonitorRecord."""

        # Parse JSON fields
        headers = json.loads(row["headers"]) if row["headers"] else {}
        response_headers = json.loads(row["response_headers"]) if row["response_headers"] else {}
        error_info = None
        if row["error_info"]:
            error_data = json.loads(row["error_info"])
            error_info = ErrorInfo(**error_data)

        request_data = RequestData(
            method=row["method"],
            path=row["path"],
            query_params=row["query_params"],
            headers=headers,
            client_ip=row["client_ip"],
            user_agent=row["user_agent"],
            request_body=row["request_body"],
            request_size=row["request_size"],
            timestamp=row["timestamp"]
        )

        response_data = ResponseData(
            status_code=row["status_code"],
            response_time=row["response_time"],
            response_size=row["response_size"],
            response_headers=response_headers,
            response_body=row["response_body"]
        )

        return MonitorRecord(
            id=row["id"],
            request=request_data,
            response=response_data,
            error_info=error_info,
            created_at=datetime.fromisoformat(row["created_at"]) if row["created_at"] else None
        )
