//! # PostgreSQL Adapter
//!
//! Adapter implementation that wraps PostgreSQL user/database management functions
//! and implements the `DatabaseProvider` port trait from `lmrc-ports`.
//!
//! This adapter allows PostgreSQL to be used interchangeably with other database
//! providers in the LMRC Stack hexagonal architecture.

use async_trait::async_trait;
use crate::{Error as PostgresError, Privilege};
use lmrc_ports::{
    CreatedDatabase, DatabaseCreateRequest, DatabaseProvider, DatabaseUser, DatabaseUserRequest,
    PortError, PortResult,
};
use lmrc_ssh::{AuthMethod, SshClient};

/// PostgreSQL adapter implementing the DatabaseProvider port
pub struct PostgresAdapter {
    host: String,
    port: u16,
    ssh_auth: AuthMethod,
}

impl PostgresAdapter {
    /// Create a new PostgreSQL adapter
    ///
    /// # Arguments
    ///
    /// * `host` - Database server hostname/IP
    /// * `port` - PostgreSQL port (usually 5432)
    /// * `ssh_auth` - SSH authentication method
    pub fn new(host: String, port: u16, ssh_auth: AuthMethod) -> Self {
        Self {
            host,
            port,
            ssh_auth,
        }
    }

    /// Create adapter from environment variables
    ///
    /// Reads:
    /// - `POSTGRES_HOST` - Database server hostname/IP
    /// - `POSTGRES_PORT` - PostgreSQL port (default: 5432)
    /// - `POSTGRES_SSH_USER` - SSH username (default: "root")
    /// - `POSTGRES_SSH_KEY` - SSH private key path (default: ~/.ssh/id_rsa)
    ///
    /// # Errors
    ///
    /// Returns error if required environment variables are missing
    pub fn from_env() -> PortResult<Self> {
        let host = std::env::var("POSTGRES_HOST").map_err(|_| {
            PortError::InvalidConfiguration(
                "POSTGRES_HOST environment variable is required".to_string(),
            )
        })?;

        let port = std::env::var("POSTGRES_PORT")
            .unwrap_or_else(|_| "5432".to_string())
            .parse()
            .map_err(|_| {
                PortError::InvalidConfiguration("Invalid POSTGRES_PORT value".to_string())
            })?;

        let ssh_user = std::env::var("POSTGRES_SSH_USER").unwrap_or_else(|_| "root".to_string());

        let ssh_key_path = std::env::var("POSTGRES_SSH_KEY").unwrap_or_else(|_| {
            let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
            format!("{}/.ssh/id_rsa", home)
        });

        let ssh_auth = AuthMethod::PublicKey {
            username: ssh_user,
            private_key_path: ssh_key_path,
            passphrase: None,
        };

        Ok(Self::new(host, port, ssh_auth))
    }

    /// Create SSH client for database operations
    async fn create_ssh_client(&self) -> PortResult<SshClient> {
        let client = SshClient::new(&self.host, 22)
            .map_err(|e| PortError::NetworkError(format!("Failed to create SSH client: {}", e)))?
            .with_auth(self.ssh_auth.clone())
            .connect()
            .map_err(|e| {
                PortError::NetworkError(format!("Failed to connect via SSH: {}", e))
            })?;

        Ok(client)
    }
}

/// Convert PostgreSQL error to PortError
fn convert_error(err: PostgresError) -> PortError {
    match err {
        PostgresError::Ssh(e) => PortError::NetworkError(format!("SSH error: {}", e)),
        PostgresError::SshExecution { message, command } => PortError::OperationFailed(format!(
            "SSH command '{}' failed: {}",
            command, message
        )),
        PostgresError::Installation(msg) => PortError::OperationFailed(format!("Installation failed: {}", msg)),
        PostgresError::Configuration(msg) => PortError::InvalidConfiguration(msg),
        PostgresError::NotInstalled => PortError::OperationFailed("PostgreSQL is not installed".to_string()),
        PostgresError::AlreadyInstalled(version) => PortError::AlreadyExists {
            resource_type: "PostgreSQL installation".to_string(),
            resource_id: version,
        },
        PostgresError::InvalidVersion(version) => {
            PortError::InvalidConfiguration(format!("Invalid PostgreSQL version: {}", version))
        }
        PostgresError::InvalidConfig { parameter, value } => {
            PortError::InvalidConfiguration(format!("Invalid config {} = {}", parameter, value))
        }
        PostgresError::MissingConfig(msg) => PortError::InvalidConfiguration(format!("Missing config: {}", msg)),
        PostgresError::ServiceError(msg) => PortError::OperationFailed(format!("Service error: {}", msg)),
        PostgresError::ConnectionTest(msg) => {
            PortError::OperationFailed(format!("Connection test failed: {}", msg))
        }
        PostgresError::Uninstallation(msg) => {
            PortError::OperationFailed(format!("Uninstallation failed: {}", msg))
        }
        PostgresError::Io(e) => PortError::OperationFailed(format!("IO error: {}", e)),
        PostgresError::Serialization(e) => PortError::OperationFailed(format!("Serialization error: {}", e)),
        PostgresError::Other(msg) => PortError::OperationFailed(msg),
    }
}

#[async_trait]
impl DatabaseProvider for PostgresAdapter {
    async fn create_database(&self, request: DatabaseCreateRequest) -> PortResult<CreatedDatabase> {
        let mut ssh = self.create_ssh_client().await?;

        // Create database with options
        // Signature: (ssh, database_name, owner, encoding, template)
        crate::create_database_with_options(
            &mut ssh,
            &request.name,
            Some(&request.owner),
            request.encoding.as_deref(),
            None, // template
        )
        .await
        .map_err(convert_error)?;

        // Build connection string
        let connection_string = format!(
            "postgresql://{}@{}:{}/{}",
            request.owner, self.host, self.port, request.name
        );

        Ok(CreatedDatabase {
            name: request.name,
            owner: request.owner,
            connection_string,
            host: self.host.clone(),
            port: self.port,
        })
    }

    async fn drop_database(&self, name: &str) -> PortResult<()> {
        let mut ssh = self.create_ssh_client().await?;

        crate::drop_database(&mut ssh, name)
            .await
            .map_err(convert_error)?;

        Ok(())
    }

    async fn list_databases(&self) -> PortResult<Vec<String>> {
        let mut ssh = self.create_ssh_client().await?;

        let databases = crate::list_databases(&mut ssh)
            .await
            .map_err(convert_error)?;

        Ok(databases.into_iter().map(|db| db.name).collect())
    }

    async fn database_exists(&self, name: &str) -> PortResult<bool> {
        let mut ssh = self.create_ssh_client().await?;

        crate::database_exists(&mut ssh, name)
            .await
            .map_err(convert_error)
    }

    async fn create_user(&self, request: DatabaseUserRequest) -> PortResult<DatabaseUser> {
        let mut ssh = self.create_ssh_client().await?;

        // Create user with options
        crate::create_user_with_options(
            &mut ssh,
            &request.username,
            &request.password,
            request.superuser,
            false, // create_db
            false, // create_role
            Some(-1),    // connection_limit (-1 = unlimited)
        )
        .await
        .map_err(convert_error)?;

        Ok(DatabaseUser {
            username: request.username,
            superuser: request.superuser,
        })
    }

    async fn drop_user(&self, username: &str) -> PortResult<()> {
        let mut ssh = self.create_ssh_client().await?;

        crate::drop_user(&mut ssh, username)
            .await
            .map_err(convert_error)?;

        Ok(())
    }

    async fn grant_privileges(&self, database: &str, username: &str) -> PortResult<()> {
        let mut ssh = self.create_ssh_client().await?;

        // Grant all privileges on the database
        // Signature: (ssh, database, username, privileges)
        crate::grant_privileges(
            &mut ssh,
            database,
            username,
            &[Privilege::All],
        )
        .await
        .map_err(convert_error)?;

        Ok(())
    }
}
