use chrono::{DateTime, Utc};
use platform_core::{Clock, DatabaseConfig, DbPool, IdGenerator};
use std::sync::Mutex;
use uuid::Uuid;

#[derive(Debug)]
pub struct FixedClock {
    now: DateTime<Utc>,
}

impl FixedClock {
    pub fn new(now: DateTime<Utc>) -> Self {
        Self { now }
    }
}

impl Clock for FixedClock {
    fn now(&self) -> DateTime<Utc> {
        self.now
    }
}

#[derive(Debug, Default)]
pub struct SequentialIdGenerator {
    next: Mutex<u64>,
}

impl IdGenerator for SequentialIdGenerator {
    fn new_id(&self, prefix: &str) -> String {
        let mut next = self.next.lock().expect("id generator lock poisoned");
        *next += 1;
        format!("{prefix}_{next}")
    }
}

#[derive(Debug)]
pub struct TestDatabase {
    admin_url: String,
    name: String,
    pub url: String,
    pub pool: DbPool,
}

impl TestDatabase {
    pub async fn create() -> Option<Self> {
        let admin_url = match std::env::var("DATABASE_URL") {
            Ok(url) => url,
            Err(_) => {
                eprintln!("skipping Postgres integration test: DATABASE_URL is not set");
                return None;
            }
        };

        let name = format!("lenso_test_{}", Uuid::now_v7().simple());
        let admin_pool = match sqlx::postgres::PgPoolOptions::new()
            .max_connections(1)
            .connect(&admin_url)
            .await
        {
            Ok(pool) => pool,
            Err(error) => {
                eprintln!(
                    "skipping Postgres integration test: could not connect to DATABASE_URL: {error}"
                );
                return None;
            }
        };

        let create_sql = format!(r#"create database "{name}""#);
        if let Err(error) = sqlx::query(sqlx::AssertSqlSafe(create_sql))
            .execute(&admin_pool)
            .await
        {
            eprintln!(
                "skipping Postgres integration test: could not create test database: {error}"
            );
            return None;
        }
        admin_pool.close().await;

        let url = database_url_with_name(&admin_url, &name);
        let pool = match platform_core::connect_pool(&DatabaseConfig {
            url: url.clone(),
            max_connections: 5,
        })
        .await
        {
            Ok(pool) => pool,
            Err(error) => {
                eprintln!(
                    "skipping Postgres integration test: could not connect to test database: {error}"
                );
                return None;
            }
        };

        Some(Self {
            admin_url,
            name,
            url,
            pool,
        })
    }

    pub async fn cleanup(self) {
        self.pool.close().await;
        if let Ok(admin_pool) = sqlx::postgres::PgPoolOptions::new()
            .max_connections(1)
            .connect(&self.admin_url)
            .await
        {
            let terminate_sql = r#"
                select pg_terminate_backend(pid)
                from pg_stat_activity
                where datname = $1
            "#;
            let _ = sqlx::query(terminate_sql)
                .bind(&self.name)
                .execute(&admin_pool)
                .await;

            let drop_sql = format!(r#"drop database if exists "{}""#, self.name);
            let _ = sqlx::query(sqlx::AssertSqlSafe(drop_sql))
                .execute(&admin_pool)
                .await;
            admin_pool.close().await;
        }
    }
}

fn database_url_with_name(url: &str, name: &str) -> String {
    let without_query = url.split_once('?').map_or(url, |(base, _)| base);
    let slash = without_query
        .rfind('/')
        .expect("DATABASE_URL must include a database name");
    format!("{}/{}", &without_query[..slash], name)
}
