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

#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
#[sea_orm(table_name = "repos")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    #[sea_orm(unique)]
    pub slug: String,
    pub provider: String,
    pub target_branch: String,
    pub developer_focus: Option<String>,
    pub custom_prompt: Option<String>,
    pub token: String,
    pub created_at: String,
}

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

impl ActiveModelBehavior for ActiveModel {}

impl Model {
    pub async fn insert_new(
        db: &DatabaseConnection,
        slug: &str,
        provider: &str,
        target_branch: &str,
        developer_focus: Option<&str>,
        custom_prompt: Option<&str>,
        token: &str,
    ) -> Result<i32> {
        let now = Utc::now().to_rfc3339();
        let active = ActiveModel {
            slug: Set(slug.to_string()),
            provider: Set(provider.to_string()),
            target_branch: Set(target_branch.to_string()),
            developer_focus: Set(developer_focus.map(|s| s.to_string())),
            custom_prompt: Set(custom_prompt.map(|s| s.to_string())),
            token: Set(token.to_string()),
            created_at: Set(now),
            ..Default::default()
        };
        let result = active.insert(db).await?;
        Ok(result.id)
    }

    pub async fn find_all(db: &DatabaseConnection) -> Result<Vec<Model>> {
        let repos = Entity::find().order_by_asc(Column::Slug).all(db).await?;
        Ok(repos)
    }

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

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

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

    /// Insert or update a repo row keyed by `slug` (remote URL). Preserves `developer_focus` and `custom_prompt` when updating.
    pub async fn upsert_registered_repo(
        db: &DatabaseConnection,
        slug: &str,
        provider: &str,
        target_branch: &str,
        token: &str,
    ) -> Result<()> {
        if let Some(existing) = Self::find_by_slug(db, slug).await? {
            Self::update_repo(
                db,
                existing.id,
                slug,
                provider,
                target_branch,
                existing.developer_focus.as_deref(),
                existing.custom_prompt.as_deref(),
                token,
            )
            .await?;
        } else {
            Self::insert_new(db, slug, provider, target_branch, None, None, token).await?;
        }
        Ok(())
    }

    #[allow(clippy::too_many_arguments)]
    pub async fn update_repo(
        db: &DatabaseConnection,
        id: i32,
        slug: &str,
        provider: &str,
        target_branch: &str,
        developer_focus: Option<&str>,
        custom_prompt: Option<&str>,
        token: &str,
    ) -> Result<u64> {
        let result = Entity::update_many()
            .col_expr(Column::Slug, Expr::value(slug))
            .col_expr(Column::Provider, Expr::value(provider))
            .col_expr(Column::TargetBranch, Expr::value(target_branch))
            .col_expr(Column::DeveloperFocus, Expr::value(developer_focus))
            .col_expr(Column::CustomPrompt, Expr::value(custom_prompt))
            .col_expr(Column::Token, Expr::value(token))
            .filter(Column::Id.eq(id))
            .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)
    }

    pub async fn validate_slugs(db: &DatabaseConnection, slugs: &[&str]) -> Result<Vec<Model>> {
        let mut repos: Vec<Model> = Vec::new();
        for slug in slugs {
            let repo = Entity::find()
                .filter(Column::Slug.contains(*slug))
                .one(db)
                .await?;
            if let Some(r) = repo {
                if !repos.iter().any(|existing| existing.id == r.id) {
                    repos.push(r);
                }
            }
        }
        Ok(repos)
    }
}
