use axum::response::Response;
use aws_local_core::db::DbPool;
use chrono::Utc;
use serde_json::Value;

use crate::docker::{DockerManager, engine_from_params};
use crate::errors::RdsError;

pub async fn create_db_instance(
    body: Value,
    db: &DbPool,
    docker: &DockerManager,
) -> Result<Response, RdsError> {
    let identifier = body["DBInstanceIdentifier"]
        .as_str()
        .ok_or_else(|| RdsError::InvalidInput("DBInstanceIdentifier required".into()))?;
    let engine = body["Engine"].as_str().unwrap_or("postgres");
    let engine_version = body["EngineVersion"].as_str().unwrap_or("16");
    let master_username = body["MasterUsername"].as_str().unwrap_or("admin");
    let master_password = body["MasterUserPassword"].as_str().unwrap_or("password");
    let db_name = body["DBName"].as_str().unwrap_or(identifier);
    let instance_class = body["DBInstanceClass"].as_str().unwrap_or("db.t3.micro");
    let allocated_storage = body["AllocatedStorage"].as_i64().unwrap_or(20);
    let cluster_id = body["DBClusterIdentifier"].as_str();

    // For cluster instances (Aurora), skip Docker — the cluster already owns the
    // container.  For standalone instances, try Docker with a fallback.
    let port = if cluster_id.is_some() {
        5432
    } else {
        let db_engine = engine_from_params(engine, engine_version);
        match docker.ensure_container(&db_engine).await {
            Ok(p) => {
                if let Err(e) = docker
                    .create_database_for_engine(&db_engine, p, db_name, master_username, master_password)
                    .await
                {
                    tracing::warn!("Docker create_database failed, using stub: {e}");
                }
                p
            }
            Err(e) => {
                tracing::warn!("Docker unavailable for RDS instance, using stub: {e}");
                5432
            }
        }
    };

    let password_hash = hash_password(master_password);
    let now = Utc::now().to_rfc3339();
    let endpoint = "localhost";

    // Scope the MutexGuard so it's dropped before the function returns
    // (keeps the async future Send)
    {
        let conn = db.lock().unwrap();
        conn.execute(
            "INSERT INTO db_instances
             (db_instance_identifier, db_instance_class, engine, engine_version,
              master_username, password_hash, db_name, allocated_storage,
              status, endpoint_address, endpoint_port, created_at, db_cluster_identifier)
             VALUES (?1,?2,?3,?4,?5,?6,?7,?8,'available',?9,?10,?11,?12)",
            rusqlite::params![
                identifier, instance_class, engine, engine_version,
                master_username, password_hash, db_name, allocated_storage,
                endpoint, port, now, cluster_id,
            ],
        ).map_err(|e| {
            if e.to_string().contains("UNIQUE") {
                RdsError::AlreadyExists(format!("DB instance {identifier} already exists"))
            } else {
                RdsError::InternalError(e.to_string())
            }
        })?;
    }

    Ok(instance_xml_response(
        "CreateDBInstance",
        identifier, instance_class, engine, engine_version,
        master_username, db_name, "available",
        endpoint, port, &now, cluster_id,
    ))
}

pub fn describe_db_instances(
    body: Value,
    db: &DbPool,
) -> Result<Response, RdsError> {
    let conn = db.lock().unwrap();
    let identifier = body["DBInstanceIdentifier"].as_str();

    if let Some(id) = identifier {
        let mut stmt = conn.prepare(
            "SELECT db_instance_identifier, db_instance_class, engine, engine_version,
                    master_username, db_name, status, endpoint_address, endpoint_port, created_at,
                    db_cluster_identifier
             FROM db_instances WHERE db_instance_identifier = ?1"
        ).map_err(|e| RdsError::InternalError(e.to_string()))?;
        let instances: Vec<String> = stmt.query_map(rusqlite::params![id], row_to_xml)
            .map_err(|e| RdsError::InternalError(e.to_string()))?
            .filter_map(|r| r.ok())
            .collect();
        if instances.is_empty() {
            return Err(RdsError::ResourceNotFound(format!("DB instance {id} not found")));
        }
        let members = instances.join("");
        return Ok(describe_instances_xml_response(&members));
    }

    let mut stmt = conn.prepare(
        "SELECT db_instance_identifier, db_instance_class, engine, engine_version,
                master_username, db_name, status, endpoint_address, endpoint_port, created_at,
                db_cluster_identifier
         FROM db_instances ORDER BY db_instance_identifier"
    ).map_err(|e| RdsError::InternalError(e.to_string()))?;
    let instances: Vec<String> = stmt.query_map([], row_to_xml)
        .map_err(|e| RdsError::InternalError(e.to_string()))?
        .filter_map(|r| r.ok())
        .collect();
    let members = instances.join("");

    Ok(describe_instances_xml_response(&members))
}

pub async fn delete_db_instance(
    body: Value,
    db: &DbPool,
    docker: &DockerManager,
    drop_on_delete: bool,
) -> Result<Response, RdsError> {
    let identifier = body["DBInstanceIdentifier"]
        .as_str()
        .ok_or_else(|| RdsError::InvalidInput("DBInstanceIdentifier required".into()))?;

    let row = {
        let conn = db.lock().unwrap();
        let row = conn.query_row(
            "SELECT db_name, master_username, endpoint_port, engine, engine_version
             FROM db_instances WHERE db_instance_identifier=?1",
            rusqlite::params![identifier],
            |r| Ok((
                r.get::<_, String>(0)?,
                r.get::<_, String>(1)?,
                r.get::<_, u16>(2)?,
                r.get::<_, String>(3)?,
                r.get::<_, String>(4)?,
            )),
        ).map_err(|_| RdsError::ResourceNotFound(format!("DB instance {identifier} not found")))?;

        conn.execute(
            "DELETE FROM db_instances WHERE db_instance_identifier=?1",
            rusqlite::params![identifier],
        ).map_err(|e| RdsError::InternalError(e.to_string()))?;

        row
    };

    if drop_on_delete {
        let db_engine = engine_from_params(&row.3, &row.4);
        docker.drop_database_for_engine(&db_engine, row.2, &row.0, &row.1).await
            .map_err(RdsError::InternalError)?;
    }

    let del_xml = format!(
        "<DBInstance>\
           <DBInstanceIdentifier>{identifier}</DBInstanceIdentifier>\
           <DBInstanceStatus>deleting</DBInstanceStatus>\
         </DBInstance>"
    );
    Ok(super::xml_response("DeleteDBInstance", &del_xml))
}

fn row_to_xml(r: &rusqlite::Row) -> rusqlite::Result<String> {
    let cluster_id: Option<String> = r.get(10)?;
    Ok(instance_xml(
        &r.get::<_,String>(0)?, &r.get::<_,String>(1)?,
        &r.get::<_,String>(2)?, &r.get::<_,String>(3)?,
        &r.get::<_,String>(4)?, &r.get::<_,String>(5)?,
        &r.get::<_,String>(6)?,
        &r.get::<_,String>(7)?, r.get::<_,u16>(8)?,
        &r.get::<_,String>(9)?, cluster_id.as_deref(),
    ))
}

fn instance_xml(
    identifier: &str, class: &str, engine: &str, version: &str,
    username: &str, db_name: &str, status: &str,
    address: &str, port: u16, created: &str,
    cluster_id: Option<&str>,
) -> String {
    let arn = format!("arn:aws:rds:us-east-1:000000000000:db:{identifier}");
    let cluster_xml = cluster_id.map(|c| format!("<DBClusterIdentifier>{c}</DBClusterIdentifier>"))
        .unwrap_or_default();
    format!(
        "<DBInstance>\
           <DBInstanceIdentifier>{identifier}</DBInstanceIdentifier>\
           <DBInstanceArn>{arn}</DBInstanceArn>\
           <DBInstanceClass>{class}</DBInstanceClass>\
           <Engine>{engine}</Engine>\
           <EngineVersion>{version}</EngineVersion>\
           <MasterUsername>{username}</MasterUsername>\
           <DBName>{db_name}</DBName>\
           <DBInstanceStatus>{status}</DBInstanceStatus>\
           <Endpoint><Address>{address}</Address><Port>{port}</Port></Endpoint>\
           <InstanceCreateTime>{created}</InstanceCreateTime>\
           <AllocatedStorage>20</AllocatedStorage>\
           <StorageType>gp2</StorageType>\
           <StorageEncrypted>true</StorageEncrypted>\
           <MultiAZ>false</MultiAZ>\
           <PubliclyAccessible>false</PubliclyAccessible>\
           <DeletionProtection>false</DeletionProtection>\
           <CopyTagsToSnapshot>false</CopyTagsToSnapshot>\
           <IAMDatabaseAuthenticationEnabled>false</IAMDatabaseAuthenticationEnabled>\
           <PerformanceInsightsEnabled>false</PerformanceInsightsEnabled>\
           <MonitoringInterval>0</MonitoringInterval>\
           {cluster_xml}\
           <TagList/>\
         </DBInstance>"
    )
}

fn instance_xml_response(
    action: &str,
    identifier: &str, class: &str, engine: &str, version: &str,
    username: &str, db_name: &str, status: &str,
    address: &str, port: u16, created: &str,
    cluster_id: Option<&str>,
) -> Response {
    let inst = instance_xml(
        identifier, class, engine, version,
        username, db_name, status,
        address, port, created, cluster_id,
    );
    super::xml_response(action, &inst)
}

fn describe_instances_xml_response(instances_xml: &str) -> Response {
    super::xml_response(
        "DescribeDBInstances",
        &format!("<DBInstances>{instances_xml}</DBInstances>"),
    )
}

fn hash_password(password: &str) -> String {
    use argon2::{Argon2, PasswordHasher};
    use argon2::password_hash::SaltString;
    let salt = SaltString::generate(&mut rand_core_old::OsRng);
    Argon2::default()
        .hash_password(password.as_bytes(), &salt)
        .map(|h| h.to_string())
        .unwrap_or_else(|_| "hash-failed".to_string())
}
