import logging
import time
import socket
import psycopg2
import redis
import requests
from typing import Callable

# Configure logging
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')

class ComponentStatusError(Exception):
    """Custom exception for component status check failures."""
    pass

class StartupOrchestrator:
    """
    Orchestrates the startup sequence of the Genesis system components.
    """

    def __init__(self):
        """
        Initializes the StartupOrchestrator with component connection details.
        """
        self.redis_host = "redis-genesis-u50607.vm.elestio.app"
        self.redis_port = 26379
        self.postgres_host = "postgresql-genesis-u50607.vm.elestio.app"
        self.postgres_port = 25432
        self.postgres_db = "postgres"
        self.postgres_user = "postgres"
        self.postgres_password = "your_postgres_password"  # Replace with actual password
        self.qdrant_host = "qdrant-b3knu-u50607.vm.elestio.app"
        self.qdrant_port = 6333
        self.aiva_host = "localhost"
        self.aiva_port = 23405
        self.dashboard_url = "http://localhost:3000" # Example Dashboard URL - replace with the actual URL

    def _check_redis(self) -> bool:
        """
        Checks the status of the Redis server.

        Returns:
            bool: True if Redis is healthy, False otherwise.
        """
        try:
            r = redis.Redis(host=self.redis_host, port=self.redis_port, db=0, socket_connect_timeout=5)
            r.ping()
            logging.info("Redis is healthy.")
            return True
        except redis.exceptions.ConnectionError as e:
            logging.error(f"Redis connection error: {e}")
            return False

    def _check_postgres(self) -> bool:
        """
        Checks the status of the PostgreSQL database.

        Returns:
            bool: True if PostgreSQL is healthy, False otherwise.
        """
        try:
            conn = psycopg2.connect(
                host=self.postgres_host,
                port=self.postgres_port,
                database=self.postgres_db,
                user=self.postgres_user,
                password=self.postgres_password,
                connect_timeout=5
            )
            cur = conn.cursor()
            cur.execute("SELECT 1")  # Simple query to check connection
            result = cur.fetchone()
            conn.close()
            logging.info("PostgreSQL is healthy.")
            return True
        except psycopg2.Error as e:
            logging.error(f"PostgreSQL connection error: {e}")
            return False

    def _check_qdrant(self) -> bool:
        """
        Checks the status of the Qdrant vector database.  Uses a basic socket connection test.

        Returns:
            bool: True if Qdrant is healthy, False otherwise.
        """
        try:
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
                s.settimeout(5)
                s.connect((self.qdrant_host, self.qdrant_port))
            logging.info("Qdrant is healthy.")
            return True
        except (socket.error, socket.timeout) as e:
            logging.error(f"Qdrant connection error: {e}")
            return False

    def _check_aiva(self) -> bool:
        """
        Checks the status of the AIVA (Ollama) service by making a GET request.

        Returns:
            bool: True if AIVA is healthy, False otherwise.
        """
        try:
            response = requests.get(f"http://{self.aiva_host}:{self.aiva_port}", timeout=5)
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
            logging.info("AIVA is healthy.")
            return True
        except requests.exceptions.RequestException as e:
            logging.error(f"AIVA connection error: {e}")
            return False

    def _check_dashboard(self) -> bool:
        """
        Checks the status of the Dashboard by making a GET request.

        Returns:
            bool: True if Dashboard is healthy, False otherwise.
        """
        try:
            response = requests.get(self.dashboard_url, timeout=5)
            response.raise_for_status()  # Raise HTTPError for bad responses (4xx or 5xx)
            logging.info("Dashboard is healthy.")
            return True
        except requests.exceptions.RequestException as e:
            logging.error(f"Dashboard connection error: {e}")
            return False

    def _start_component(self, component_name: str, check_function: Callable[[], bool]) -> None:
        """
        Starts and validates the health of a component.

        Args:
            component_name (str): The name of the component.
            check_function (Callable[[], bool]): A function that checks the component's health.

        Raises:
            ComponentStatusError: If the component fails to start within a reasonable time.
        """
        start_time = time.time()
        timeout = 60  # Maximum time to wait for the component to become healthy (seconds)
        check_interval = 5  # How often to check the component's status (seconds)

        logging.info(f"Starting {component_name}...")
        while time.time() - start_time < timeout:
            if check_function():
                logging.info(f"{component_name} started successfully.")
                return
            time.sleep(check_interval)

        raise ComponentStatusError(f"Failed to start {component_name} within {timeout} seconds.")


    def orchestrate_startup(self) -> None:
        """
        Orchestrates the startup sequence of the Genesis system.
        """
        try:
            self._start_component("Redis", self._check_redis)
            self._start_component("PostgreSQL", self._check_postgres)
            self._start_component("Qdrant", self._check_qdrant)
            self._start_component("AIVA", self._check_aiva)
            self._start_component("Dashboard", self._check_dashboard)

            logging.info("Genesis system startup complete.")

        except ComponentStatusError as e:
            logging.error(f"Startup failed: {e}")


if __name__ == "__main__":
    orchestrator = StartupOrchestrator()
    orchestrator.orchestrate_startup()
