use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum StateBackend {
    Memory,
    Sqlite,
    Rocksdb,
    Chainfire,
    #[default]
    Flaredb,
    Postgres,
}

impl fmt::Display for StateBackend {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        let value = match self {
            Self::Memory => "memory",
            Self::Sqlite => "sqlite",
            Self::Rocksdb => "rocksdb",
            Self::Chainfire => "chainfire",
            Self::Flaredb => "flaredb",
            Self::Postgres => "postgres",
        };
        f.write_str(value)
    }
}

impl FromStr for StateBackend {
    type Err = anyhow::Error;

    fn from_str(value: &str) -> Result<Self, Self::Err> {
        match value.trim().to_ascii_lowercase().as_str() {
            "memory" => Ok(Self::Memory),
            "sqlite" => Ok(Self::Sqlite),
            "rocksdb" => Ok(Self::Rocksdb),
            "chainfire" => Ok(Self::Chainfire),
            "flaredb" => Ok(Self::Flaredb),
            "postgres" => Ok(Self::Postgres),
            other => Err(anyhow::anyhow!(
                "invalid state backend '{}'; expected one of: memory, sqlite, rocksdb, chainfire, flaredb, postgres",
                other
            )),
        }
    }
}

pub fn ensure_sql_backend_matches_url(
    backend: StateBackend,
    database_url: &str,
) -> anyhow::Result<()> {
    let normalized = database_url.trim().to_ascii_lowercase();
    match backend {
        StateBackend::Postgres => {
            if normalized.starts_with("postgres://") || normalized.starts_with("postgresql://") {
                Ok(())
            } else {
                Err(anyhow::anyhow!(
                    "state backend=postgres requires postgres:// or postgresql:// URL"
                ))
            }
        }
        StateBackend::Sqlite => {
            if normalized.starts_with("sqlite:") {
                Ok(())
            } else {
                Err(anyhow::anyhow!("state backend=sqlite requires sqlite: URL"))
            }
        }
        _ => Ok(()),
    }
}

#[cfg(test)]
mod tests {
    use super::{ensure_sql_backend_matches_url, StateBackend};
    use std::str::FromStr;

    #[test]
    fn parses_known_backends() {
        assert_eq!(
            StateBackend::from_str("flaredb").unwrap(),
            StateBackend::Flaredb
        );
        assert_eq!(
            StateBackend::from_str("sqlite").unwrap(),
            StateBackend::Sqlite
        );
    }

    #[test]
    fn validates_sql_urls() {
        ensure_sql_backend_matches_url(StateBackend::Postgres, "postgres://db").unwrap();
        ensure_sql_backend_matches_url(StateBackend::Sqlite, "sqlite:/tmp/db.sqlite").unwrap();
        assert!(ensure_sql_backend_matches_url(StateBackend::Sqlite, "postgres://db").is_err());
    }
}
