"""
Base Integration Classes

Provides circuit breaker pattern and base classes for external integrations.
"""

import time
import asyncio
from typing import Callable, Any, Optional, Dict
from dataclasses import dataclass
from datetime import datetime, timedelta
from enum import Enum

from ..config import mcp_config
from storage.json_store import Cache, read_json, write_json, SYSTEM_DIR


class CircuitState(Enum):
    """Circuit breaker states"""
    CLOSED = "closed"  # Normal operation
    OPEN = "open"  # Failing, reject requests
    HALF_OPEN = "half_open"  # Testing if service recovered


@dataclass
class CircuitBreakerStats:
    """Circuit breaker statistics"""
    state: CircuitState
    failure_count: int
    success_count: int
    last_failure_time: Optional[float]
    last_success_time: Optional[float]
    opened_at: Optional[float]
    next_retry_time: Optional[float]


class CircuitBreakerOpen(Exception):
    """Exception raised when circuit breaker is open"""
    pass


class CircuitBreaker:
    """
    Circuit Breaker pattern implementation.

    Protects external service calls from cascading failures.
    Three states: CLOSED (normal), OPEN (failing), HALF_OPEN (testing).
    """

    def __init__(
        self,
        integration_type: str,
        failure_threshold: int = None,
        timeout_seconds: int = None,
        half_open_max_calls: int = 3
    ):
        """
        Args:
            integration_type: Type of integration (figma, jira, confluence, etc.)
            failure_threshold: Number of failures before opening circuit
            timeout_seconds: Seconds to wait before trying again
            half_open_max_calls: Max successful calls in half-open before closing
        """
        self.integration_type = integration_type
        self.failure_threshold = failure_threshold or mcp_config.CIRCUIT_BREAKER_FAILURE_THRESHOLD
        self.timeout_seconds = timeout_seconds or mcp_config.CIRCUIT_BREAKER_TIMEOUT_SECONDS
        self.half_open_max_calls = half_open_max_calls

        # In-memory state (could be moved to Redis for distributed setup)
        self.state = CircuitState.CLOSED
        self.failure_count = 0
        self.success_count = 0
        self.last_failure_time: Optional[float] = None
        self.last_success_time: Optional[float] = None
        self.opened_at: Optional[float] = None

    async def call(self, func: Callable, *args, **kwargs) -> Any:
        """
        Call a function through the circuit breaker.

        Args:
            func: Function to call (can be sync or async)
            *args, **kwargs: Arguments to pass to func

        Returns:
            Function result

        Raises:
            CircuitBreakerOpen: If circuit is open
            Exception: Original exception from func if it fails
        """
        # Check circuit state
        if self.state == CircuitState.OPEN:
            # Check if timeout has elapsed
            if time.time() - self.opened_at < self.timeout_seconds:
                await self._record_failure("Circuit breaker is OPEN", db_only=True)
                raise CircuitBreakerOpen(
                    f"{self.integration_type} service is temporarily unavailable. "
                    f"Retry after {self._seconds_until_retry():.0f}s"
                )
            else:
                # Timeout elapsed, move to HALF_OPEN
                self.state = CircuitState.HALF_OPEN
                self.success_count = 0

        # Execute function
        try:
            # Handle both sync and async functions
            if asyncio.iscoroutinefunction(func):
                result = await func(*args, **kwargs)
            else:
                result = func(*args, **kwargs)

            # Success!
            await self._record_success()

            # If in HALF_OPEN, check if we can close the circuit
            if self.state == CircuitState.HALF_OPEN:
                if self.success_count >= self.half_open_max_calls:
                    self.state = CircuitState.CLOSED
                    self.failure_count = 0

            return result

        except Exception as e:
            # Failure
            await self._record_failure(str(e))

            # Check if we should open the circuit
            if self.failure_count >= self.failure_threshold:
                self.state = CircuitState.OPEN
                self.opened_at = time.time()

            raise

    async def _record_success(self):
        """Record successful call"""
        self.success_count += 1
        self.last_success_time = time.time()

        # Update database
        await self._update_health_db(is_healthy=True, error=None)

    async def _record_failure(self, error_message: str, db_only: bool = False):
        """Record failed call"""
        if not db_only:
            self.failure_count += 1
            self.last_failure_time = time.time()

        # Update database
        await self._update_health_db(is_healthy=False, error=error_message)

    async def _update_health_db(self, is_healthy: bool, error: Optional[str]):
        """Update integration health in database"""
        loop = asyncio.get_event_loop()

        def update_db():
            try:
                with get_connection() as conn:
                    circuit_open_until = None
                    if self.state == CircuitState.OPEN and self.opened_at:
                        circuit_open_until = datetime.fromtimestamp(
                            self.opened_at + self.timeout_seconds
                        ).isoformat()

                    if is_healthy:
                        conn.execute(
                            """
                            UPDATE integration_health
                            SET is_healthy = 1,
                                failure_count = 0,
                                last_success_at = CURRENT_TIMESTAMP,
                                circuit_open_until = NULL,
                                updated_at = CURRENT_TIMESTAMP
                            WHERE integration_type = ?
                            """,
                            (self.integration_type,)
                        )
                    else:
                        conn.execute(
                            """
                            UPDATE integration_health
                            SET is_healthy = 0,
                                failure_count = ?,
                                last_failure_at = CURRENT_TIMESTAMP,
                                circuit_open_until = ?,
                                updated_at = CURRENT_TIMESTAMP
                            WHERE integration_type = ?
                            """,
                            (self.failure_count, circuit_open_until, self.integration_type)
                        )
            except Exception as e:
                print(f"Error updating integration health: {e}")

        await loop.run_in_executor(None, update_db)

    def _seconds_until_retry(self) -> float:
        """Get seconds until circuit can be retried"""
        if self.state != CircuitState.OPEN or not self.opened_at:
            return 0
        elapsed = time.time() - self.opened_at
        remaining = self.timeout_seconds - elapsed
        return max(0, remaining)

    def get_stats(self) -> CircuitBreakerStats:
        """Get current circuit breaker statistics"""
        next_retry_time = None
        if self.state == CircuitState.OPEN and self.opened_at:
            next_retry_time = self.opened_at + self.timeout_seconds

        return CircuitBreakerStats(
            state=self.state,
            failure_count=self.failure_count,
            success_count=self.success_count,
            last_failure_time=self.last_failure_time,
            last_success_time=self.last_success_time,
            opened_at=self.opened_at,
            next_retry_time=next_retry_time
        )


class BaseIntegration:
    """Base class for all external integrations"""

    def __init__(self, integration_type: str, config: Dict[str, Any]):
        """
        Args:
            integration_type: Type of integration (figma, jira, etc.)
            config: Integration configuration (decrypted)
        """
        self.integration_type = integration_type
        self.config = config
        self.circuit_breaker = CircuitBreaker(integration_type)

    async def call_api(self, func: Callable, *args, **kwargs) -> Any:
        """
        Call external API through circuit breaker.

        Args:
            func: API function to call
            *args, **kwargs: Arguments to pass

        Returns:
            API response

        Raises:
            CircuitBreakerOpen: If circuit is open
            Exception: Original API exception
        """
        return await self.circuit_breaker.call(func, *args, **kwargs)

    def get_health(self) -> Dict[str, Any]:
        """Get integration health status"""
        stats = self.circuit_breaker.get_stats()
        return {
            "integration_type": self.integration_type,
            "state": stats.state.value,
            "is_healthy": stats.state == CircuitState.CLOSED,
            "failure_count": stats.failure_count,
            "success_count": stats.success_count,
            "last_failure_time": stats.last_failure_time,
            "last_success_time": stats.last_success_time,
            "next_retry_time": stats.next_retry_time
        }
