"""
Database operations for saved user machines.
"""

import os
from typing import Any, Dict, List, Optional

import psycopg2
import structlog
from psycopg2.extras import RealDictCursor
from psycopg2.extensions import connection as Connection

logger = structlog.get_logger(__name__)


class SavedMachinesDB:
    """PostgreSQL operations for saved machines."""

    def __init__(self) -> None:
        """Initialize database configuration."""
        self.db_config = {
            "host": os.getenv("POSTGRES_HOST", "postgres"),
            "port": int(os.getenv("POSTGRES_PORT", "5432")),
            "database": os.getenv("POSTGRES_DB", "guacamole_db"),
            "user": os.getenv("POSTGRES_USER", "guacamole_user"),
            "password": os.getenv("POSTGRES_PASSWORD"),
        }

    def _get_connection(self) -> Connection:
        """Get database connection."""
        try:
            return psycopg2.connect(**self.db_config)
        except Exception as e:
            logger.error("Failed to connect to database", error=str(e))
            raise
    
    def create_machine(
        self,
        user_id: str,
        name: str,
        hostname: str,
        port: int,
        protocol: str,
        os: Optional[str] = None,
        description: Optional[str] = None,
        tags: Optional[List[str]] = None,
        is_favorite: bool = False,
    ) -> Dict[str, Any]:
        """
        Create new saved machine.

        Args:
            user_id: User ID.
            name: Machine name.
            hostname: Machine hostname.
            port: Connection port.
            protocol: Connection protocol.
            os: Operating system (optional).
            description: Description (optional).
            tags: Tags list (optional).
            is_favorite: Whether machine is favorite.

        Returns:
            Dictionary with created machine data including ID.
        """
        with self._get_connection() as conn:
            try:
                with conn.cursor(cursor_factory=RealDictCursor) as cur:
                    query = """
                        INSERT INTO api.user_saved_machines
                        (user_id, name, hostname, port, protocol, os,
                         description, tags, is_favorite)
                        VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s)
                        RETURNING id, user_id, name, hostname, port, protocol, os,
                                  description, tags, is_favorite, created_at, updated_at,
                                  last_connected_at
                    """

                    cur.execute(
                        query,
                        (
                            user_id,
                            name,
                            hostname,
                            port,
                            protocol,
                            os,
                            description,
                            tags or [],
                            is_favorite,
                        ),
                    )

                    result = dict(cur.fetchone())
                    conn.commit()

                    logger.info(
                        "Saved machine created",
                        machine_id=result["id"],
                        user_id=user_id,
                        name=name,
                    )

                    return result

            except Exception as e:
                conn.rollback()
                logger.error(
                    "Failed to create saved machine", error=str(e), user_id=user_id
                )
                raise
    
    def get_user_machines(
        self, user_id: str, include_stats: bool = False
    ) -> List[Dict[str, Any]]:
        """
        Get all user machines.

        Args:
            user_id: User ID.
            include_stats: Include connection statistics.

        Returns:
            List of machine dictionaries.
        """
        with self._get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                if include_stats:
                    query = """
                        SELECT
                            m.*,
                            json_build_object(
                                'total_connections', COALESCE(COUNT(h.id), 0),
                                'last_connection', MAX(h.connected_at),
                                'successful_connections',
                                    COALESCE(SUM(CASE WHEN h.success = TRUE THEN 1 ELSE 0 END), 0),
                                'failed_connections',
                                    COALESCE(SUM(CASE WHEN h.success = FALSE THEN 1 ELSE 0 END), 0)
                            ) as connection_stats
                        FROM api.user_saved_machines m
                        LEFT JOIN api.connection_history h ON m.id = h.machine_id
                        WHERE m.user_id = %s
                        GROUP BY m.id
                        ORDER BY m.is_favorite DESC, m.updated_at DESC
                    """
                else:
                    query = """
                        SELECT * FROM api.user_saved_machines
                        WHERE user_id = %s
                        ORDER BY is_favorite DESC, updated_at DESC
                    """

                cur.execute(query, (user_id,))
                results = [dict(row) for row in cur.fetchall()]

                logger.debug(
                    "Retrieved user machines", user_id=user_id, count=len(results)
                )

                return results
    
    def get_machine_by_id(
        self, machine_id: str, user_id: str
    ) -> Optional[Dict[str, Any]]:
        """
        Get machine by ID with owner verification.

        Args:
            machine_id: Machine UUID.
            user_id: User ID for permission check.

        Returns:
            Machine dictionary or None if not found.
        """
        with self._get_connection() as conn:
            with conn.cursor(cursor_factory=RealDictCursor) as cur:
                query = """
                    SELECT * FROM api.user_saved_machines
                    WHERE id = %s AND user_id = %s
                """

                cur.execute(query, (machine_id, user_id))
                result = cur.fetchone()

                return dict(result) if result else None
    
    def update_machine(
        self, machine_id: str, user_id: str, **updates: Any
    ) -> Optional[Dict[str, Any]]:
        """
        Update machine.

        Args:
            machine_id: Machine UUID.
            user_id: User ID for permission check.
            **updates: Fields to update.

        Returns:
            Updated machine dictionary or None if not found.
        """
        allowed_fields = {
            "name",
            "hostname",
            "port",
            "protocol",
            "os",
            "description",
            "tags",
            "is_favorite",
        }

        updates_filtered = {
            k: v for k, v in updates.items() if k in allowed_fields and v is not None
        }

        if not updates_filtered:
            return self.get_machine_by_id(machine_id, user_id)

        set_clause = ", ".join([f"{k} = %s" for k in updates_filtered.keys()])
        values = list(updates_filtered.values()) + [machine_id, user_id]

        query = f"""
            UPDATE api.user_saved_machines
            SET {set_clause}
            WHERE id = %s AND user_id = %s
            RETURNING id, user_id, name, hostname, port, protocol, os,
                      description, tags, is_favorite, created_at, updated_at,
                      last_connected_at
        """

        with self._get_connection() as conn:
            try:
                with conn.cursor(cursor_factory=RealDictCursor) as cur:
                    cur.execute(query, values)
                    result = cur.fetchone()
                    conn.commit()

                    if result:
                        logger.info(
                            "Saved machine updated",
                            machine_id=machine_id,
                            user_id=user_id,
                            updated_fields=list(updates_filtered.keys()),
                        )
                        return dict(result)

                    return None

            except Exception as e:
                conn.rollback()
                logger.error(
                    "Failed to update machine",
                    error=str(e),
                    machine_id=machine_id,
                    user_id=user_id,
                )
                raise
    
    def delete_machine(self, machine_id: str, user_id: str) -> bool:
        """
        Delete machine.

        Args:
            machine_id: Machine UUID.
            user_id: User ID for permission check.

        Returns:
            True if deleted, False if not found.
        """
        with self._get_connection() as conn:
            try:
                with conn.cursor() as cur:
                    query = """
                        DELETE FROM api.user_saved_machines
                        WHERE id = %s AND user_id = %s
                    """

                    cur.execute(query, (machine_id, user_id))
                    deleted_count = cur.rowcount
                    conn.commit()

                    if deleted_count > 0:
                        logger.info(
                            "Saved machine deleted",
                            machine_id=machine_id,
                            user_id=user_id,
                        )
                        return True

                    logger.warning(
                        "Machine not found for deletion",
                        machine_id=machine_id,
                        user_id=user_id,
                    )
                    return False

            except Exception as e:
                conn.rollback()
                logger.error(
                    "Failed to delete machine",
                    error=str(e),
                    machine_id=machine_id,
                    user_id=user_id,
                )
                raise
    
    def update_last_connected(self, machine_id: str, user_id: str) -> None:
        """
        Update last connection time.

        Args:
            machine_id: Machine UUID.
            user_id: User ID.
        """
        with self._get_connection() as conn:
            try:
                with conn.cursor() as cur:
                    query = """
                        UPDATE api.user_saved_machines
                        SET last_connected_at = NOW()
                        WHERE id = %s AND user_id = %s
                    """

                    cur.execute(query, (machine_id, user_id))
                    conn.commit()

                    logger.debug(
                        "Updated last_connected_at",
                        machine_id=machine_id,
                        user_id=user_id,
                    )

            except Exception as e:
                conn.rollback()
                logger.error("Failed to update last_connected", error=str(e))
    
    def add_connection_history(
        self,
        user_id: str,
        machine_id: str,
        success: bool = True,
        error_message: Optional[str] = None,
        duration_seconds: Optional[int] = None,
        client_ip: Optional[str] = None,
    ) -> str:
        """
        Add connection history record.

        Args:
            user_id: User ID.
            machine_id: Machine ID.
            success: Whether connection was successful.
            error_message: Error message if failed (optional).
            duration_seconds: Connection duration in seconds (optional).
            client_ip: Client IP address (optional).

        Returns:
            UUID of created record.
        """
        with self._get_connection() as conn:
            try:
                with conn.cursor(cursor_factory=RealDictCursor) as cur:
                    query = """
                        INSERT INTO api.connection_history
                        (user_id, machine_id, success, error_message, duration_seconds, client_ip)
                        VALUES (%s, %s, %s, %s, %s, %s)
                        RETURNING id
                    """

                    cur.execute(
                        query,
                        (
                            user_id,
                            machine_id,
                            success,
                            error_message,
                            duration_seconds,
                            client_ip,
                        ),
                    )

                    result = cur.fetchone()
                    conn.commit()

                    logger.info(
                        "Connection history record created",
                        machine_id=machine_id,
                        user_id=user_id,
                        success=success,
                    )

                    return str(result["id"])

            except Exception as e:
                conn.rollback()
                logger.error("Failed to add connection history", error=str(e))
                raise


saved_machines_db = SavedMachinesDB()

