"""
Escalation Repository

Repository pattern for escalation request storage, supporting both
in-memory (testing) and PostgreSQL (production) implementations.
"""

import json
import logging
from abc import ABC, abstractmethod
from datetime import UTC, datetime
from typing import Any

from sage.core.escalation import (
    EscalationPriority,
    EscalationRequest,
    EscalationStatus,
    EscalationTrigger,
)

logger = logging.getLogger(__name__)


class EscalationRepository(ABC):
    """Abstract base class for escalation storage.

    Defines the interface for storing and retrieving escalation requests.
    Implementations can use different backends (in-memory, PostgreSQL, etc.).
    """

    @abstractmethod
    async def add(self, request: EscalationRequest) -> str:
        """Add a new escalation request.

        Args:
            request: The escalation request to store.

        Returns:
            The request ID.
        """
        pass

    @abstractmethod
    async def get(self, request_id: str) -> EscalationRequest | None:
        """Get an escalation request by ID.

        Args:
            request_id: The request ID to look up.

        Returns:
            The escalation request, or None if not found.
        """
        pass

    @abstractmethod
    async def update(self, request: EscalationRequest) -> bool:
        """Update an existing escalation request.

        Args:
            request: The updated escalation request.

        Returns:
            True if successful, False if request not found.
        """
        pass

    @abstractmethod
    async def remove(self, request_id: str) -> bool:
        """Remove an escalation request.

        Args:
            request_id: The request ID to remove.

        Returns:
            True if successful, False if request not found.
        """
        pass

    @abstractmethod
    async def get_pending(self) -> list[EscalationRequest]:
        """Get all pending escalation requests.

        Returns:
            List of pending escalation requests.
        """
        pass

    @abstractmethod
    async def get_by_priority(
        self, priority: EscalationPriority
    ) -> list[EscalationRequest]:
        """Get escalation requests by priority.

        Args:
            priority: The priority level to filter by.

        Returns:
            List of matching escalation requests.
        """
        pass

    @abstractmethod
    async def get_by_user(self, user_id: str) -> list[EscalationRequest]:
        """Get escalation requests by user ID.

        Args:
            user_id: The user ID to filter by.

        Returns:
            List of matching escalation requests.
        """
        pass

    @abstractmethod
    async def count_pending(self) -> int:
        """Count pending escalation requests.

        Returns:
            Number of pending requests.
        """
        pass


class InMemoryEscalationRepository(EscalationRepository):
    """In-memory implementation for testing and development.

    This implementation stores escalation requests in a dictionary.
    Data is lost when the process terminates.
    """

    def __init__(self) -> None:
        """Initialize the in-memory repository."""
        self._requests: dict[str, EscalationRequest] = {}
        logger.info("Initialized InMemoryEscalationRepository")

    async def add(self, request: EscalationRequest) -> str:
        """Add a new escalation request."""
        self._requests[request.request_id] = request
        logger.info(f"Added escalation request {request.request_id} to in-memory store")
        return request.request_id

    async def get(self, request_id: str) -> EscalationRequest | None:
        """Get an escalation request by ID."""
        return self._requests.get(request_id)

    async def update(self, request: EscalationRequest) -> bool:
        """Update an existing escalation request."""
        if request.request_id in self._requests:
            self._requests[request.request_id] = request
            return True
        return False

    async def remove(self, request_id: str) -> bool:
        """Remove an escalation request."""
        if request_id in self._requests:
            del self._requests[request_id]
            return True
        return False

    async def get_pending(self) -> list[EscalationRequest]:
        """Get all pending escalation requests."""
        return [
            r for r in self._requests.values()
            if r.status == EscalationStatus.PENDING
        ]

    async def get_by_priority(
        self, priority: EscalationPriority
    ) -> list[EscalationRequest]:
        """Get escalation requests by priority."""
        return [
            r for r in self._requests.values()
            if r.priority == priority and r.status == EscalationStatus.PENDING
        ]

    async def get_by_user(self, user_id: str) -> list[EscalationRequest]:
        """Get escalation requests by user ID."""
        return [
            r for r in self._requests.values()
            if r.user_id == user_id
        ]

    async def count_pending(self) -> int:
        """Count pending escalation requests."""
        return len(await self.get_pending())

    async def clear(self) -> None:
        """Clear all requests (for testing)."""
        self._requests.clear()


class PostgresEscalationRepository(EscalationRepository):
    """PostgreSQL implementation for production use.

    This implementation stores escalation requests in a PostgreSQL database
    for persistence and reliability.

    Table schema:
        CREATE TABLE escalation_requests (
            request_id UUID PRIMARY KEY,
            created_at TIMESTAMP NOT NULL,
            query TEXT NOT NULL,
            proposed_response TEXT NOT NULL,
            context JSONB,
            triggers TEXT[] NOT NULL,
            priority VARCHAR(20) NOT NULL,
            status VARCHAR(20) NOT NULL,
            assigned_reviewer VARCHAR(255),
            reviewer_notes TEXT,
            modified_response TEXT,
            reviewed_at TIMESTAMP,
            reviewed_by VARCHAR(255),
            user_id VARCHAR(255),
            user_notified BOOLEAN DEFAULT FALSE,
            response_held BOOLEAN DEFAULT TRUE
        );

        CREATE INDEX idx_escalation_status ON escalation_requests(status);
        CREATE INDEX idx_escalation_priority ON escalation_requests(priority);
        CREATE INDEX idx_escalation_user ON escalation_requests(user_id);
    """

    def __init__(self, connection_string: str) -> None:
        """Initialize the PostgreSQL repository.

        Args:
            connection_string: PostgreSQL connection string (async format).
        """
        self._connection_string = connection_string
        self._pool = None
        logger.info("Initialized PostgresEscalationRepository")

    async def _get_pool(self) -> Any:
        """Get or create connection pool.

        Returns:
            asyncpg connection pool.
        """
        if self._pool is None:
            try:
                import asyncpg
                self._pool = await asyncpg.create_pool(
                    self._connection_string,
                    min_size=1,
                    max_size=10,
                )
            except ImportError:
                raise ImportError(
                    "asyncpg is required for PostgreSQL support. "
                    "Install it with: pip install asyncpg"
                )
        return self._pool

    async def close(self) -> None:
        """Close the connection pool."""
        if self._pool:
            await self._pool.close()
            self._pool = None

    async def add(self, request: EscalationRequest) -> str:
        """Add a new escalation request."""
        pool = await self._get_pool()

        query = """
            INSERT INTO escalation_requests (
                request_id, created_at, query, proposed_response, context,
                triggers, priority, status, assigned_reviewer, reviewer_notes,
                modified_response, reviewed_at, reviewed_by, user_id,
                user_notified, response_held
            ) VALUES (
                $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16
            )
        """

        async with pool.acquire() as conn:
            await conn.execute(
                query,
                request.request_id,
                request.created_at,
                request.query,
                request.proposed_response,
                json.dumps(request.context),
                [t.value for t in request.triggers],
                request.priority.value,
                request.status.value,
                request.assigned_reviewer,
                request.reviewer_notes,
                request.modified_response,
                request.reviewed_at,
                request.reviewed_by,
                request.user_id,
                request.user_notified,
                request.response_held,
            )

        logger.info(f"Added escalation request {request.request_id} to PostgreSQL")
        return request.request_id

    async def get(self, request_id: str) -> EscalationRequest | None:
        """Get an escalation request by ID."""
        pool = await self._get_pool()

        query = """
            SELECT * FROM escalation_requests WHERE request_id = $1
        """

        async with pool.acquire() as conn:
            row = await conn.fetchrow(query, request_id)

        if row is None:
            return None

        return self._row_to_request(row)

    async def update(self, request: EscalationRequest) -> bool:
        """Update an existing escalation request."""
        pool = await self._get_pool()

        query = """
            UPDATE escalation_requests SET
                query = $2,
                proposed_response = $3,
                context = $4,
                triggers = $5,
                priority = $6,
                status = $7,
                assigned_reviewer = $8,
                reviewer_notes = $9,
                modified_response = $10,
                reviewed_at = $11,
                reviewed_by = $12,
                user_id = $13,
                user_notified = $14,
                response_held = $15
            WHERE request_id = $1
        """

        async with pool.acquire() as conn:
            result = await conn.execute(
                query,
                request.request_id,
                request.query,
                request.proposed_response,
                json.dumps(request.context),
                [t.value for t in request.triggers],
                request.priority.value,
                request.status.value,
                request.assigned_reviewer,
                request.reviewer_notes,
                request.modified_response,
                request.reviewed_at,
                request.reviewed_by,
                request.user_id,
                request.user_notified,
                request.response_held,
            )

        # asyncpg returns "UPDATE N" where N is number of rows affected
        return "UPDATE 1" in result

    async def remove(self, request_id: str) -> bool:
        """Remove an escalation request."""
        pool = await self._get_pool()

        query = "DELETE FROM escalation_requests WHERE request_id = $1"

        async with pool.acquire() as conn:
            result = await conn.execute(query, request_id)

        return "DELETE 1" in result

    async def get_pending(self) -> list[EscalationRequest]:
        """Get all pending escalation requests."""
        pool = await self._get_pool()

        query = """
            SELECT * FROM escalation_requests
            WHERE status = $1
            ORDER BY created_at ASC
        """

        async with pool.acquire() as conn:
            rows = await conn.fetch(query, EscalationStatus.PENDING.value)

        return [self._row_to_request(row) for row in rows]

    async def get_by_priority(
        self, priority: EscalationPriority
    ) -> list[EscalationRequest]:
        """Get escalation requests by priority."""
        pool = await self._get_pool()

        query = """
            SELECT * FROM escalation_requests
            WHERE priority = $1 AND status = $2
            ORDER BY created_at ASC
        """

        async with pool.acquire() as conn:
            rows = await conn.fetch(
                query, priority.value, EscalationStatus.PENDING.value
            )

        return [self._row_to_request(row) for row in rows]

    async def get_by_user(self, user_id: str) -> list[EscalationRequest]:
        """Get escalation requests by user ID."""
        pool = await self._get_pool()

        query = """
            SELECT * FROM escalation_requests
            WHERE user_id = $1
            ORDER BY created_at DESC
        """

        async with pool.acquire() as conn:
            rows = await conn.fetch(query, user_id)

        return [self._row_to_request(row) for row in rows]

    async def count_pending(self) -> int:
        """Count pending escalation requests."""
        pool = await self._get_pool()

        query = "SELECT COUNT(*) FROM escalation_requests WHERE status = $1"

        async with pool.acquire() as conn:
            result = await conn.fetchval(query, EscalationStatus.PENDING.value)

        return result or 0

    def _row_to_request(self, row: Any) -> EscalationRequest:
        """Convert a database row to an EscalationRequest.

        Args:
            row: Database row (asyncpg Record).

        Returns:
            EscalationRequest instance.
        """
        # Parse triggers from string array
        triggers = [EscalationTrigger(t) for t in row["triggers"]]

        # Parse context from JSON
        context = json.loads(row["context"]) if row["context"] else {}

        return EscalationRequest(
            request_id=str(row["request_id"]),
            created_at=row["created_at"].replace(tzinfo=UTC) if row["created_at"] else datetime.now(UTC),
            query=row["query"],
            proposed_response=row["proposed_response"],
            context=context,
            triggers=triggers,
            priority=EscalationPriority(row["priority"]),
            status=EscalationStatus(row["status"]),
            assigned_reviewer=row["assigned_reviewer"],
            reviewer_notes=row["reviewer_notes"],
            modified_response=row["modified_response"],
            reviewed_at=row["reviewed_at"].replace(tzinfo=UTC) if row["reviewed_at"] else None,
            reviewed_by=row["reviewed_by"],
            user_id=row["user_id"],
            user_notified=row["user_notified"] or False,
            response_held=row["response_held"] if row["response_held"] is not None else True,
        )


# SQL for creating the escalation_requests table
CREATE_TABLE_SQL = """
CREATE TABLE IF NOT EXISTS escalation_requests (
    request_id UUID PRIMARY KEY,
    created_at TIMESTAMP NOT NULL,
    query TEXT NOT NULL,
    proposed_response TEXT NOT NULL,
    context JSONB,
    triggers TEXT[] NOT NULL,
    priority VARCHAR(20) NOT NULL,
    status VARCHAR(20) NOT NULL,
    assigned_reviewer VARCHAR(255),
    reviewer_notes TEXT,
    modified_response TEXT,
    reviewed_at TIMESTAMP,
    reviewed_by VARCHAR(255),
    user_id VARCHAR(255),
    user_notified BOOLEAN DEFAULT FALSE,
    response_held BOOLEAN DEFAULT TRUE
);

CREATE INDEX IF NOT EXISTS idx_escalation_status ON escalation_requests(status);
CREATE INDEX IF NOT EXISTS idx_escalation_priority ON escalation_requests(priority);
CREATE INDEX IF NOT EXISTS idx_escalation_user ON escalation_requests(user_id);
"""
