use sqlx::types::chrono::{self, NaiveDateTime};
use sqlx::{FromRow, MySqlPool};

#[derive(Debug, Clone, FromRow)]
pub struct Job {
    pub id: String,
    pub target: String,
    pub action: String,
    pub path: String,
    pub created_at: NaiveDateTime,
    pub expires_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub status: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JobTarget {
    TiDb(String),
    Qdrant,
}

impl TryFrom<String> for JobTarget {
    type Error = String;

    fn try_from(value: String) -> Result<Self, Self::Error> {
        match value.as_str() {
            "Qdrant" => Ok(JobTarget::Qdrant),
            _ => {
                if value.starts_with("TiDb") {
                    let value = value.replace("\"", "");
                    let parts: Vec<&str> = value.splitn(2, '(').collect();
                    if parts.len() == 2 {
                        let inner = parts[1].trim_end_matches(')');
                        return Ok(JobTarget::TiDb(inner.to_string()));
                    }
                    return Err("Unknown target".to_string());
                }
                Err("Unknown target".to_string())
            }
        }
    }
}

pub type TenantToken = String;

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JobAction {
    Delete,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum JobStatus {
    Pending,
    Processing,
    Done,
}

impl Job {
    pub fn new(
        target: JobTarget,
        action: JobAction,
        path: String,
        expired: std::time::Duration,
    ) -> Self {
        let now = chrono::Utc::now().naive_utc();
        Self {
            id: uuid::Uuid::new_v4().to_string(),
            target: format!("{:?}", target),
            action: format!("{:?}", action),
            path,
            created_at: now,
            expires_at: now + expired,
            updated_at: now,
            status: format!("{:?}", JobStatus::Pending),
        }
    }

    pub fn target(&self) -> anyhow::Result<JobTarget> {
        match self.target.clone().try_into() {
            Ok(target) => Ok(target),
            Err(e) => Err(anyhow::anyhow!(e)),
        }
    }

    pub fn action(&self) -> anyhow::Result<JobAction> {
        match self.action.as_str() {
            "Delete" => Ok(JobAction::Delete),
            _ => Err(anyhow::anyhow!("Unknown action")),
        }
    }

    pub fn status(&self) -> anyhow::Result<JobStatus> {
        match self.status.as_str() {
            "Pending" => Ok(JobStatus::Pending),
            "Processing" => Ok(JobStatus::Processing),
            "Done" => Ok(JobStatus::Done),
            _ => Err(anyhow::anyhow!("Unknown status")),
        }
    }

    pub async fn push(&self, pool: &MySqlPool) -> sqlx::Result<String> {
        sqlx::query(
            r#"
            INSERT INTO jobs_queue (id, target, action, path, expires_at)
            VALUES (?, ?, ?, ?, ?)
            "#,
        )
        .bind(&self.id)
        .bind(self.target.to_string())
        .bind(self.action.to_string())
        .bind(&self.path)
        .bind(self.expires_at)
        .execute(pool)
        .await?;

        Ok(self.id.clone())
    }

    pub async fn get_by_id(pool: &MySqlPool, id: &str) -> sqlx::Result<Option<Job>> {
        sqlx::query_as::<_, Job>(
            r#"
            SELECT id, target, action, path, created_at, expires_at, updated_at, `status` FROM jobs_queue WHERE id = ?
            "#,
        )
        .bind(id)
        .fetch_optional(pool)
        .await
    }

    pub async fn cleanup_id(pool: &MySqlPool, id: &str) -> sqlx::Result<()> {
        sqlx::query(
            r#"
            DELETE FROM jobs_queue WHERE id = ?
            "#,
        )
        .bind(id)
        .execute(pool)
        .await?;

        Ok(())
    }

    pub async fn update_status(pool: &MySqlPool, id: &str, status: JobStatus) -> sqlx::Result<()> {
        sqlx::query(
            r#"
            UPDATE jobs_queue SET `status` = ? WHERE id = ?
            "#,
        )
        .bind(format!("{:?}", status))
        .bind(id)
        .execute(pool)
        .await?;

        Ok(())
    }
}
#[cfg(test)]
mod tests {
    use std::time::Duration;

    use sqlx::mysql::MySqlPoolOptions;

    use super::*;

    fn get_dsn() -> String {
        std::env::var("DSN").unwrap()
    }

    async fn setup_db() -> MySqlPool {
        MySqlPoolOptions::new()
            .max_connections(5)
            .connect(&get_dsn())
            .await
            .expect("Failed to create pool")
    }

    #[tokio::test]
    async fn test_create_job() {
        let job = Job::new(
            JobTarget::Qdrant,
            JobAction::Delete,
            "/path/to/resource".to_string(),
            Duration::from_secs(3600),
        );

        assert_eq!(job.target.to_string(), "Qdrant");
        assert!(job.expires_at > job.created_at);

        let job = Job::new(
            JobTarget::TiDb("abc".to_string()),
            JobAction::Delete,
            "default".to_string(),
            Duration::from_secs(0),
        );
        assert_eq!(job.target.to_string(), "TiDb(\"abc\")");
        assert!(job.expires_at == job.created_at);
    }

    #[tokio::test]
    async fn test_push() {
        let pool = setup_db().await;
        let target = JobTarget::TiDb("abc".to_string());
        let action = JobAction::Delete;
        let job = Job::new(
            target.clone(),
            action.clone(),
            "tidb_delete_queue".to_string(),
            Duration::from_secs(3600),
        );

        job.push(&pool).await.unwrap();

        let job_from_db = Job::get_by_id(&pool, &job.id).await.unwrap().unwrap();
        assert_eq!(job_from_db.id, job.id);
        assert_eq!(job_from_db.target().unwrap(), target);
        assert_eq!(job_from_db.action().unwrap(), action);

        Job::cleanup_id(&pool, &job.id).await.unwrap();
    }
}
