use anyhow::Result;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, IntoActiveModel, QueryOrder};
use serde::{Deserialize, Serialize};

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "tasks")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    #[sea_orm(unique)]
    pub jira_key: String,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {}

impl ActiveModelBehavior for ActiveModel {}

// --- Validation ---

pub fn validate_repo_url(url: &str) -> Result<()> {
    if url.is_empty() {
        anyhow::bail!("repo_url cannot be empty");
    }
    if !url.starts_with("git://") {
        anyhow::bail!("repo_url must use the git:// protocol, got: {}", url);
    }
    Ok(())
}

// --- CRUD impl ---

impl Model {
    pub async fn upsert_with_repo(
        db: &DatabaseConnection,
        jira_key: &str,
        summary: &str,
        description: &str,
        repo_url: &str,
        assignee: Option<&str>,
    ) -> Result<()> {
        validate_repo_url(repo_url)?;
        let repo = super::repo::Entity::find()
            .filter(super::repo::Column::Slug.eq(repo_url))
            .one(db)
            .await?
            .ok_or_else(|| anyhow::anyhow!("No registered repo matches repo_url '{}'", repo_url))?;

        let existing = Entity::find()
            .filter(Column::JiraKey.eq(jira_key))
            .one(db)
            .await?;

        let task_id = match existing {
            Some(row) => {
                let tid = row.id;
                let mut active: ActiveModel = row.into_active_model();
                active.jira_key = Set(jira_key.to_string());
                active.update(db).await?;
                tid
            }
            None => {
                let active = ActiveModel {
                    jira_key: Set(jira_key.to_string()),
                    ..Default::default()
                };
                let inserted = active.insert(db).await?;
                inserted.id
            }
        };

        super::task_execution::Model::upsert_pending_work(
            db,
            task_id,
            repo.id,
            summary,
            Some(description),
            assignee,
            super::task_execution::EngineType::ClaudeCode,
        )
        .await?;

        Ok(())
    }

    pub async fn find_pending(db: &DatabaseConnection) -> Result<Vec<Model>> {
        let execs = super::task_execution::Model::find_pending_executions(db).await?;
        let mut tasks = Vec::with_capacity(execs.len());
        for ex in execs {
            if let Some(task) = Entity::find_by_id(ex.task_id).one(db).await? {
                tasks.push(task);
            }
        }
        Ok(tasks)
    }

    pub async fn find_all_tasks(db: &DatabaseConnection) -> Result<Vec<Model>> {
        Ok(Entity::find().order_by_desc(Column::Id).all(db).await?)
    }

    pub async fn find_by_key(db: &DatabaseConnection, jira_key: &str) -> Result<Option<Model>> {
        let task = Entity::find()
            .filter(Column::JiraKey.eq(jira_key))
            .one(db)
            .await?;
        Ok(task)
    }

    pub async fn find_by_id(db: &DatabaseConnection, id: i32) -> Result<Option<Model>> {
        let task = Entity::find_by_id(id).one(db).await?;
        Ok(task)
    }

    pub async fn set_in_progress(db: &DatabaseConnection, jira_key: &str) -> Result<()> {
        let Some(t) = Self::find_by_key(db, jira_key).await? else {
            return Ok(());
        };
        super::task_execution::Model::set_in_progress_for_task(db, t.id).await
    }

    pub async fn set_done(
        db: &DatabaseConnection,
        jira_key: &str,
        branch_name: &str,
        pr_url: &str,
    ) -> Result<()> {
        let Some(t) = Self::find_by_key(db, jira_key).await? else {
            return Ok(());
        };
        super::task_execution::Model::set_done_for_task(db, t.id, branch_name, pr_url).await
    }

    pub async fn set_failed(db: &DatabaseConnection, jira_key: &str, error: &str) -> Result<()> {
        let Some(t) = Self::find_by_key(db, jira_key).await? else {
            return Ok(());
        };
        super::task_execution::Model::set_failed_for_task(db, t.id, error).await
    }

    pub async fn set_unfinished(
        db: &DatabaseConnection,
        jira_key: &str,
        error: &str,
    ) -> Result<()> {
        let Some(t) = Self::find_by_key(db, jira_key).await? else {
            return Ok(());
        };
        super::task_execution::Model::set_unfinished_for_task(db, t.id, error).await
    }

    pub async fn reset_to_pending(db: &DatabaseConnection, jira_key: &str) -> Result<()> {
        let Some(t) = Self::find_by_key(db, jira_key).await? else {
            return Ok(());
        };
        super::task_execution::Model::reset_latest_to_pending(db, t.id).await
    }

    pub async fn reset_all(db: &DatabaseConnection) -> Result<u64> {
        let result = Entity::delete_many().exec(db).await?;
        Ok(result.rows_affected)
    }

    pub async fn delete(db: &DatabaseConnection, id: i32) -> Result<u64> {
        let result = Entity::delete_by_id(id).exec(db).await?;
        Ok(result.rows_affected)
    }
}
