use anyhow::Result;
use chrono::Utc;
use sea_orm::entity::prelude::*;
use sea_orm::{ActiveValue::Set, Condition, IntoActiveModel, QueryOrder};
use serde::{Deserialize, Serialize};
use std::collections::HashSet;

#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
pub enum EngineType {
    #[sea_orm(string_value = "codex")]
    Codex,
    #[sea_orm(string_value = "claude_code")]
    ClaudeCode,
}

impl std::fmt::Display for EngineType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EngineType::Codex => write!(f, "codex"),
            EngineType::ClaudeCode => write!(f, "claude_code"),
        }
    }
}

#[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize)]
#[sea_orm(rs_type = "String", db_type = "String(StringLen::None)")]
pub enum TaskStatus {
    #[sea_orm(string_value = "pending")]
    Pending,
    #[sea_orm(string_value = "in_progress")]
    InProgress,
    #[sea_orm(string_value = "done")]
    Done,
    #[sea_orm(string_value = "failed")]
    Failed,
    #[sea_orm(string_value = "unfinished")]
    Unfinished,
}

impl std::fmt::Display for TaskStatus {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TaskStatus::Pending => write!(f, "pending"),
            TaskStatus::InProgress => write!(f, "in_progress"),
            TaskStatus::Done => write!(f, "done"),
            TaskStatus::Failed => write!(f, "failed"),
            TaskStatus::Unfinished => write!(f, "unfinished"),
        }
    }
}

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "task_executions")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub task_id: i32,
    pub repo_id: i32,
    pub engine_type: EngineType,
    pub summary: String,
    pub description: Option<String>,
    pub assignee: Option<String>,
    pub status: TaskStatus,
    pub branch_name: Option<String>,
    pub pr_url: Option<String>,
    pub error_message: Option<String>,
    pub fetched_at: String,
    pub processed_at: Option<String>,
    pub started_at: Option<String>,
    pub finished_at: Option<String>,
}

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

impl ActiveModelBehavior for ActiveModel {}

impl Model {
    pub async fn find_by_task(db: &DatabaseConnection, task_id: i32) -> Result<Vec<Model>> {
        let rows = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .order_by_asc(Column::Id)
            .all(db)
            .await?;
        Ok(rows)
    }

    pub async fn find_pending_executions(db: &DatabaseConnection) -> Result<Vec<Model>> {
        let rows = Entity::find()
            .filter(Column::Status.eq(TaskStatus::Pending))
            .order_by_asc(Column::FetchedAt)
            .all(db)
            .await?;
        Ok(rows)
    }

    /// Latest execution row per `task_id` (by highest `id`), for task lists.
    pub async fn find_latest_per_task(db: &DatabaseConnection) -> Result<Vec<Model>> {
        let all = Entity::find().order_by_desc(Column::Id).all(db).await?;
        let mut seen = HashSet::new();
        let mut out = Vec::new();
        for row in all {
            if seen.insert(row.task_id) {
                out.push(row);
            }
        }
        Ok(out)
    }

    pub async fn find_latest_for_task(
        db: &DatabaseConnection,
        task_id: i32,
    ) -> Result<Option<Model>> {
        let row = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .order_by_desc(Column::Id)
            .one(db)
            .await?;
        Ok(row)
    }

    pub async fn find_in_progress_for_task(
        db: &DatabaseConnection,
        task_id: i32,
    ) -> Result<Option<Model>> {
        let row = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(Column::Status.eq(TaskStatus::InProgress))
            .one(db)
            .await?;
        Ok(row)
    }

    pub async fn upsert_pending_work(
        db: &DatabaseConnection,
        task_id: i32,
        repo_id: i32,
        summary: &str,
        description: Option<&str>,
        assignee: Option<&str>,
        engine_type: EngineType,
    ) -> Result<()> {
        let now = Utc::now().to_rfc3339();
        let pending = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(Column::Status.eq(TaskStatus::Pending))
            .one(db)
            .await?;
        match pending {
            Some(row) => {
                let mut active: ActiveModel = row.into_active_model();
                active.repo_id = Set(repo_id);
                active.summary = Set(summary.to_string());
                active.description = Set(description.map(|s| s.to_string()));
                active.assignee = Set(assignee.map(|s| s.to_string()));
                active.engine_type = Set(engine_type);
                active.branch_name = Set(None);
                active.pr_url = Set(None);
                active.error_message = Set(None);
                active.fetched_at = Set(now.clone());
                active.processed_at = Set(None);
                active.started_at = Set(None);
                active.finished_at = Set(None);
                active.update(db).await?;
            }
            None => {
                let active = ActiveModel {
                    task_id: Set(task_id),
                    repo_id: Set(repo_id),
                    engine_type: Set(engine_type),
                    summary: Set(summary.to_string()),
                    description: Set(description.map(|s| s.to_string())),
                    assignee: Set(assignee.map(|s| s.to_string())),
                    status: Set(TaskStatus::Pending),
                    branch_name: Set(None),
                    pr_url: Set(None),
                    error_message: Set(None),
                    fetched_at: Set(now),
                    processed_at: Set(None),
                    started_at: Set(None),
                    finished_at: Set(None),
                    ..Default::default()
                };
                active.insert(db).await?;
            }
        }
        Ok(())
    }

    pub async fn set_in_progress_for_task(db: &DatabaseConnection, task_id: i32) -> Result<()> {
        let now = Utc::now().to_rfc3339();
        let Some(row) = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(Column::Status.eq(TaskStatus::Pending))
            .one(db)
            .await?
        else {
            return Ok(());
        };
        Entity::update_many()
            .col_expr(
                Column::Status,
                Expr::value(TaskStatus::InProgress.to_string()),
            )
            .col_expr(Column::StartedAt, Expr::value(now))
            .filter(Column::Id.eq(row.id))
            .exec(db)
            .await?;
        Ok(())
    }

    pub async fn set_done_for_task(
        db: &DatabaseConnection,
        task_id: i32,
        branch_name: &str,
        pr_url: &str,
    ) -> Result<()> {
        let now = Utc::now().to_rfc3339();
        let Some(row) = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(Column::Status.eq(TaskStatus::InProgress))
            .one(db)
            .await?
        else {
            return Ok(());
        };
        Entity::update_many()
            .col_expr(Column::Status, Expr::value(TaskStatus::Done.to_string()))
            .col_expr(Column::BranchName, Expr::value(branch_name))
            .col_expr(Column::PrUrl, Expr::value(pr_url))
            .col_expr(Column::ProcessedAt, Expr::value(now.clone()))
            .col_expr(Column::FinishedAt, Expr::value(now))
            .filter(Column::Id.eq(row.id))
            .exec(db)
            .await?;
        Ok(())
    }

    pub async fn set_failed_for_task(
        db: &DatabaseConnection,
        task_id: i32,
        error: &str,
    ) -> Result<()> {
        let now = Utc::now().to_rfc3339();
        let row = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(
                Condition::any()
                    .add(Column::Status.eq(TaskStatus::InProgress))
                    .add(Column::Status.eq(TaskStatus::Pending)),
            )
            .order_by_desc(Column::Id)
            .one(db)
            .await?;

        let Some(row) = row else {
            return Ok(());
        };
        Entity::update_many()
            .col_expr(Column::Status, Expr::value(TaskStatus::Failed.to_string()))
            .col_expr(Column::ErrorMessage, Expr::value(error))
            .col_expr(Column::ProcessedAt, Expr::value(now.clone()))
            .col_expr(Column::FinishedAt, Expr::value(now))
            .filter(Column::Id.eq(row.id))
            .exec(db)
            .await?;
        Ok(())
    }

    pub async fn set_unfinished_for_task(
        db: &DatabaseConnection,
        task_id: i32,
        error: &str,
    ) -> Result<()> {
        let now = Utc::now().to_rfc3339();
        let row = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .filter(
                Condition::any()
                    .add(Column::Status.eq(TaskStatus::InProgress))
                    .add(Column::Status.eq(TaskStatus::Pending)),
            )
            .order_by_desc(Column::Id)
            .one(db)
            .await?;

        let Some(row) = row else {
            return Ok(());
        };
        Entity::update_many()
            .col_expr(
                Column::Status,
                Expr::value(TaskStatus::Unfinished.to_string()),
            )
            .col_expr(Column::ErrorMessage, Expr::value(error))
            .col_expr(Column::ProcessedAt, Expr::value(now.clone()))
            .col_expr(Column::FinishedAt, Expr::value(now))
            .filter(Column::Id.eq(row.id))
            .exec(db)
            .await?;
        Ok(())
    }

    pub async fn reset_latest_to_pending(db: &DatabaseConnection, task_id: i32) -> Result<()> {
        let Some(row) = Entity::find()
            .filter(Column::TaskId.eq(task_id))
            .order_by_desc(Column::Id)
            .one(db)
            .await?
        else {
            return Ok(());
        };
        Entity::update_many()
            .col_expr(Column::Status, Expr::value(TaskStatus::Pending.to_string()))
            .col_expr(Column::ErrorMessage, Expr::value(Option::<String>::None))
            .col_expr(Column::BranchName, Expr::value(Option::<String>::None))
            .col_expr(Column::PrUrl, Expr::value(Option::<String>::None))
            .col_expr(Column::ProcessedAt, Expr::value(Option::<String>::None))
            .col_expr(Column::StartedAt, Expr::value(Option::<String>::None))
            .col_expr(Column::FinishedAt, Expr::value(Option::<String>::None))
            .filter(Column::Id.eq(row.id))
            .exec(db)
            .await?;
        Ok(())
    }
}
