use serde::Serialize;
use serde_json::{Value, json};
use sqlx::PgPool;

use crate::managed_files::update_file_status;
use crate::schema::ensure_storage_runtime_schema;
use crate::store::S3CredentialStoreError;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
pub enum StorageFileMutationStatus {
    Success,
    Failed,
}

impl StorageFileMutationStatus {
    fn as_audit_status(self) -> &'static str {
        match self {
            Self::Success => "success",
            Self::Failed => "error",
        }
    }
}

#[derive(Debug, Clone, Serialize)]
pub struct StorageFileMutationRecord {
    pub organization_id: String,
    pub user_id: Option<String>,
    pub route: String,
    pub bucket: String,
    pub key: String,
    pub mime_type: Option<String>,
    pub size_bytes: Option<i64>,
    pub metadata: Value,
    pub request_payload: Value,
    pub result_payload: Value,
    pub message: String,
    pub status: StorageFileMutationStatus,
}

pub async fn ensure_storage_file_catalog_tables(
    pool: &PgPool,
) -> Result<(), S3CredentialStoreError> {
    ensure_storage_runtime_schema(pool).await?;
    Ok(())
}

pub async fn record_storage_file_update(
    pool: &PgPool,
    record: &StorageFileMutationRecord,
) -> Result<String, S3CredentialStoreError> {
    ensure_storage_file_catalog_tables(pool).await?;

    let file_id = storage_file_id(&record.bucket, &record.key);
    if record.status == StorageFileMutationStatus::Success {
        let _ = update_file_status(
            pool,
            &file_id,
            "uploaded",
            record.size_bytes,
            record.mime_type.as_deref(),
            None,
            Some(json!({
                "resource": file_resource(record, &file_id),
                "objectMetadata": record.metadata,
                "lastUpdateResult": record.result_payload,
            })),
        )
        .await;
    }

    sqlx::query(
        r#"
        INSERT INTO athena.storage_audit_events (
            operation,
            file_id,
            bucket,
            storage_key,
            actor_user_id,
            organization_id,
            status,
            error,
            metadata
        )
        VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
        "#,
    )
    .bind("storage.object.update")
    .bind(&file_id)
    .bind(&record.bucket)
    .bind(&record.key)
    .bind(&record.user_id)
    .bind(&record.organization_id)
    .bind(record.status.as_audit_status())
    .bind(if record.status == StorageFileMutationStatus::Failed {
        Some(record.message.clone())
    } else {
        None
    })
    .bind(json!({
        "route": record.route,
        "mime_type": record.mime_type,
        "size_bytes": record.size_bytes,
        "request": record.request_payload,
        "result": record.result_payload,
        "resource": file_resource(record, &file_id),
        "metadata": record.metadata,
        "message": record.message,
    }))
    .execute(pool)
    .await?;

    Ok(file_id)
}

fn storage_file_id(bucket: &str, key: &str) -> String {
    format!("s3://{}/{}", bucket.trim(), key.trim_start_matches('/'))
}

fn file_resource(record: &StorageFileMutationRecord, file_id: &str) -> Value {
    json!({
        "id": file_id,
        "bucket": record.bucket,
        "key": record.key,
        "organization_id": record.organization_id,
        "mime_type": record.mime_type,
        "size_bytes": record.size_bytes,
    })
}

#[cfg(test)]
mod tests {
    use super::storage_file_id;

    #[test]
    fn storage_file_id_uses_bucket_and_key() {
        assert_eq!(
            storage_file_id("bucket-a", "/reports/file.csv"),
            "s3://bucket-a/reports/file.csv"
        );
    }
}
