use aws_local_core::db::DbPool;
use chrono::Utc;
use serde_json::Value;
use std::collections::HashMap;

use crate::arn;

#[derive(Debug)]
pub enum StoreError {
    NotFound(String),
    AlreadyExists(String),
    Internal(String),
}

/// Create a resource. Returns (arn, full_properties_with_arn).
pub fn create(
    db: &DbPool,
    service: &str,
    resource_type: &str,
    identifier: Option<&str>,
    mut properties: Value,
    region: &str,
) -> Result<(String, Value), StoreError> {
    let id = identifier
        .map(|s| s.to_string())
        .unwrap_or_else(|| arn::generate_id(service, resource_type));
    let resource_arn =
        arn::generate_arn(service, resource_type, &id, region, "000000000000");
    let now = Utc::now().to_rfc3339();

    // Inject ARN and ID into properties
    if let Some(obj) = properties.as_object_mut() {
        obj.insert("Arn".to_string(), Value::String(resource_arn.clone()));
        obj.insert("ResourceId".to_string(), Value::String(id.clone()));
    }

    let props_str =
        serde_json::to_string(&properties).map_err(|e| StoreError::Internal(e.to_string()))?;

    {
        let conn = db.lock().unwrap();
        conn.execute(
            "INSERT INTO resources (arn, service, type, identifier, region, properties, created_at, updated_at)
             VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)",
            rusqlite::params![resource_arn, service, resource_type, id, region, props_str, now, now],
        )
        .map_err(|e| {
            if e.to_string().contains("UNIQUE") {
                StoreError::AlreadyExists(format!(
                    "{service}/{resource_type}/{id} already exists"
                ))
            } else {
                StoreError::Internal(e.to_string())
            }
        })?;
    }

    Ok((resource_arn, properties))
}

/// Describe a single resource by ARN.
pub fn describe(db: &DbPool, arn_value: &str) -> Result<Value, StoreError> {
    let conn = db.lock().unwrap();
    let props: String = conn
        .query_row(
            "SELECT properties FROM resources WHERE arn = ?1",
            rusqlite::params![arn_value],
            |r| r.get(0),
        )
        .map_err(|_| StoreError::NotFound(format!("Resource not found: {arn_value}")))?;
    serde_json::from_str(&props).map_err(|e| StoreError::Internal(e.to_string()))
}

/// List resources by service and type, with optional filters on properties.
pub fn list(
    db: &DbPool,
    service: &str,
    resource_type: &str,
    filters: &HashMap<String, String>,
) -> Result<Vec<Value>, StoreError> {
    let conn = db.lock().unwrap();
    let mut stmt = conn
        .prepare(
            "SELECT properties FROM resources WHERE service = ?1 AND type = ?2 ORDER BY created_at",
        )
        .map_err(|e| StoreError::Internal(e.to_string()))?;

    let rows: Vec<Value> = stmt
        .query_map(rusqlite::params![service, resource_type], |r| {
            let s: String = r.get(0)?;
            Ok(serde_json::from_str(&s).unwrap_or(Value::Null))
        })
        .map_err(|e| StoreError::Internal(e.to_string()))?
        .filter_map(|r| r.ok())
        .filter(|v| matches_filters(v, filters))
        .collect();

    Ok(rows)
}

/// Look up a resource by service, type, and identifier.
pub fn lookup(
    db: &DbPool,
    service: &str,
    resource_type: &str,
    identifier: &str,
) -> Result<Value, StoreError> {
    let conn = db.lock().unwrap();
    let props: String = conn
        .query_row(
            "SELECT properties FROM resources WHERE service = ?1 AND type = ?2 AND identifier = ?3",
            rusqlite::params![service, resource_type, identifier],
            |r| r.get(0),
        )
        .map_err(|_| {
            StoreError::NotFound(format!(
                "{service}/{resource_type}/{identifier} not found"
            ))
        })?;
    serde_json::from_str(&props).map_err(|e| StoreError::Internal(e.to_string()))
}

/// Update a resource's properties (merge).
pub fn update(db: &DbPool, arn_value: &str, updates: Value) -> Result<Value, StoreError> {
    let now = Utc::now().to_rfc3339();
    let mut current = describe(db, arn_value)?;

    if let (Some(cur), Some(upd)) = (current.as_object_mut(), updates.as_object()) {
        for (k, v) in upd {
            cur.insert(k.clone(), v.clone());
        }
    }

    let props_str =
        serde_json::to_string(&current).map_err(|e| StoreError::Internal(e.to_string()))?;

    {
        let conn = db.lock().unwrap();
        conn.execute(
            "UPDATE resources SET properties = ?1, updated_at = ?2 WHERE arn = ?3",
            rusqlite::params![props_str, now, arn_value],
        )
        .map_err(|e| StoreError::Internal(e.to_string()))?;
    }

    Ok(current)
}

/// Update a resource found by service + type + identifier.
pub fn update_by_lookup(
    db: &DbPool,
    service: &str,
    resource_type: &str,
    identifier: &str,
    updates: Value,
) -> Result<Value, StoreError> {
    let arn_value: String = {
        let conn = db.lock().unwrap();
        conn.query_row(
            "SELECT arn FROM resources WHERE service = ?1 AND type = ?2 AND identifier = ?3",
            rusqlite::params![service, resource_type, identifier],
            |r| r.get(0),
        )
        .map_err(|_| {
            StoreError::NotFound(format!(
                "{service}/{resource_type}/{identifier} not found"
            ))
        })?
    };
    update(db, &arn_value, updates)
}

/// Delete a resource by ARN.
pub fn delete(db: &DbPool, arn_value: &str) -> Result<(), StoreError> {
    let conn = db.lock().unwrap();
    let n = conn
        .execute(
            "DELETE FROM resources WHERE arn = ?1",
            rusqlite::params![arn_value],
        )
        .map_err(|e| StoreError::Internal(e.to_string()))?;
    if n == 0 {
        return Err(StoreError::NotFound(format!(
            "Resource not found: {arn_value}"
        )));
    }
    Ok(())
}

/// Delete a resource by service + type + identifier.
pub fn delete_by_id(
    db: &DbPool,
    service: &str,
    resource_type: &str,
    identifier: &str,
) -> Result<(), StoreError> {
    let conn = db.lock().unwrap();
    let n = conn
        .execute(
            "DELETE FROM resources WHERE service = ?1 AND type = ?2 AND identifier = ?3",
            rusqlite::params![service, resource_type, identifier],
        )
        .map_err(|e| StoreError::Internal(e.to_string()))?;
    if n == 0 {
        return Err(StoreError::NotFound(format!(
            "{service}/{resource_type}/{identifier} not found"
        )));
    }
    Ok(())
}

fn matches_filters(value: &Value, filters: &HashMap<String, String>) -> bool {
    if filters.is_empty() {
        return true;
    }
    for (key, expected) in filters {
        match value.get(key).and_then(|v| v.as_str()) {
            Some(actual) if actual == expected => {}
            _ => return false,
        }
    }
    true
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::db;

    fn setup() -> DbPool {
        let pool = db::connect_in_memory().unwrap();
        db::run_migrations(&pool).unwrap();
        pool
    }

    #[test]
    fn create_and_describe() {
        let db = setup();
        let props = serde_json::json!({"CidrBlock": "10.0.0.0/16"});
        let (arn, result) = create(&db, "ec2", "vpc", None, props, "us-east-1").unwrap();
        assert!(arn.starts_with("arn:aws:ec2:us-east-1:000000000000:vpc/vpc-"));
        assert_eq!(result["CidrBlock"], "10.0.0.0/16");
        assert!(result["Arn"].as_str().unwrap().contains("vpc-"));

        let fetched = describe(&db, &arn).unwrap();
        assert_eq!(fetched["CidrBlock"], "10.0.0.0/16");
    }

    #[test]
    fn create_with_explicit_identifier() {
        let db = setup();
        let (arn, _) = create(
            &db,
            "ssm",
            "parameter",
            Some("/my/param"),
            serde_json::json!({"Value": "hello"}),
            "us-east-1",
        )
        .unwrap();
        assert!(arn.contains("/my/param"));
    }

    #[test]
    fn list_filters() {
        let db = setup();
        create(
            &db,
            "ec2",
            "subnet",
            None,
            serde_json::json!({"VpcId": "vpc-aaa"}),
            "us-east-1",
        )
        .unwrap();
        create(
            &db,
            "ec2",
            "subnet",
            None,
            serde_json::json!({"VpcId": "vpc-bbb"}),
            "us-east-1",
        )
        .unwrap();

        let all = list(&db, "ec2", "subnet", &HashMap::new()).unwrap();
        assert_eq!(all.len(), 2);

        let filtered = list(
            &db,
            "ec2",
            "subnet",
            &HashMap::from([("VpcId".to_string(), "vpc-aaa".to_string())]),
        )
        .unwrap();
        assert_eq!(filtered.len(), 1);
    }

    #[test]
    fn update_merges() {
        let db = setup();
        let (arn, _) = create(
            &db,
            "ec2",
            "instance",
            None,
            serde_json::json!({"State": "pending"}),
            "us-east-1",
        )
        .unwrap();
        let updated = update(&db, &arn, serde_json::json!({"State": "running"})).unwrap();
        assert_eq!(updated["State"], "running");
    }

    #[test]
    fn delete_works() {
        let db = setup();
        let (arn, _) =
            create(&db, "ec2", "vpc", None, serde_json::json!({}), "us-east-1").unwrap();
        delete(&db, &arn).unwrap();
        assert!(describe(&db, &arn).is_err());
    }

    #[test]
    fn delete_not_found() {
        let db = setup();
        assert!(delete(&db, "arn:aws:ec2:us-east-1:000:vpc/nope").is_err());
    }
}
