use crate::AppService;
use crate::error::AppError;
use models::pull_request::{self as pr_module, ReviewState, pull_request_review};
use models::repos::repo_branch_protect;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct BranchProtectionResponse {
    pub id: i64,
    pub repo: Uuid,
    pub branch: String,
    pub forbid_push: bool,
    pub forbid_pull: bool,
    pub forbid_merge: bool,
    pub forbid_deletion: bool,
    pub forbid_force_push: bool,
    pub forbid_tag_push: bool,
    pub required_approvals: i32,
    pub dismiss_stale_reviews: bool,
    pub require_linear_history: bool,
    pub allow_fork_syncing: bool,
}

impl From<repo_branch_protect::Model> for BranchProtectionResponse {
    fn from(m: repo_branch_protect::Model) -> Self {
        Self {
            id: m.id,
            repo: m.repo,
            branch: m.branch,
            forbid_push: m.forbid_push,
            forbid_pull: m.forbid_pull,
            forbid_merge: m.forbid_merge,
            forbid_deletion: m.forbid_deletion,
            forbid_force_push: m.forbid_force_push,
            forbid_tag_push: m.forbid_tag_push,
            required_approvals: m.required_approvals,
            dismiss_stale_reviews: m.dismiss_stale_reviews,
            require_linear_history: m.require_linear_history,
            allow_fork_syncing: m.allow_fork_syncing,
        }
    }
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct BranchProtectionCreateRequest {
    pub branch: String,
    #[serde(default)]
    pub forbid_push: bool,
    #[serde(default)]
    pub forbid_pull: bool,
    #[serde(default)]
    pub forbid_merge: bool,
    #[serde(default)]
    pub forbid_deletion: bool,
    #[serde(default)]
    pub forbid_force_push: bool,
    #[serde(default)]
    pub forbid_tag_push: bool,
    #[serde(default)]
    pub required_approvals: i32,
    #[serde(default)]
    pub dismiss_stale_reviews: bool,
    #[serde(default)]
    pub require_linear_history: bool,
    #[serde(default = "default_allow_fork_syncing")]
    pub allow_fork_syncing: bool,
}

fn default_allow_fork_syncing() -> bool {
    true
}

#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct BranchProtectionUpdateRequest {
    pub branch: Option<String>,
    pub forbid_push: Option<bool>,
    pub forbid_pull: Option<bool>,
    pub forbid_merge: Option<bool>,
    pub forbid_deletion: Option<bool>,
    pub forbid_force_push: Option<bool>,
    pub forbid_tag_push: Option<bool>,
    pub required_approvals: Option<i32>,
    pub dismiss_stale_reviews: Option<bool>,
    pub require_linear_history: Option<bool>,
    pub allow_fork_syncing: Option<bool>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ApprovalCheckResult {
    pub enough_approvals: bool,
    pub approvals: i32,
    pub required: i32,
    pub reviewers: Vec<ReviewerInfo>,
}

#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ReviewerInfo {
    pub reviewer: Uuid,
    pub state: String,
    pub submitted_at: Option<chrono::DateTime<chrono::Utc>>,
}

impl AppService {
    /// List all branch protection rules for a repository.
    pub async fn branch_protection_list(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<Vec<BranchProtectionResponse>, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let rules = repo_branch_protect::Entity::find()
            .filter(repo_branch_protect::Column::Repo.eq(repo.id))
            .all(&self.db)
            .await?;
        Ok(rules
            .into_iter()
            .map(BranchProtectionResponse::from)
            .collect())
    }

    /// Get a single branch protection rule by id.
    pub async fn branch_protection_get(
        &self,
        namespace: String,
        repo_name: String,
        rule_id: i64,
        ctx: &Session,
    ) -> Result<BranchProtectionResponse, AppError> {
        let _ = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
        let rule = repo_branch_protect::Entity::find_by_id(rule_id)
            .filter(repo_branch_protect::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Branch protection rule not found".to_string()))?;
        Ok(BranchProtectionResponse::from(rule))
    }

    /// Create a branch protection rule.
    pub async fn branch_protection_create(
        &self,
        namespace: String,
        repo_name: String,
        request: BranchProtectionCreateRequest,
        ctx: &Session,
    ) -> Result<BranchProtectionResponse, AppError> {
        let repo = self
            .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
            .await?;
        let active = repo_branch_protect::ActiveModel {
            id: Default::default(),
            repo: Set(repo.id),
            branch: Set(request.branch.clone()),
            forbid_push: Set(request.forbid_push),
            forbid_pull: Set(request.forbid_pull),
            forbid_merge: Set(request.forbid_merge),
            forbid_deletion: Set(request.forbid_deletion),
            forbid_force_push: Set(request.forbid_force_push),
            forbid_tag_push: Set(request.forbid_tag_push),
            required_approvals: Set(request.required_approvals),
            dismiss_stale_reviews: Set(request.dismiss_stale_reviews),
            require_linear_history: Set(request.require_linear_history),
            allow_fork_syncing: Set(request.allow_fork_syncing),
        };
        let rule = active.insert(&self.db).await?;
        let _ = self
            .project_log_activity(
                repo.project,
                Some(repo.id),
                ctx.user().unwrap_or(Uuid::nil()),
                super::super::project::activity::ActivityLogParams {
                    event_type: "branch_protection_create".to_string(),
                    title: format!(
                        "Branch protection created for '{}' on branch '{}'",
                        repo_name, request.branch
                    ),
                    repo_id: Some(repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({ "branch": request.branch })),
                    is_private: false,
                },
            )
            .await;
        Ok(BranchProtectionResponse::from(rule))
    }

    /// Update a branch protection rule.
    pub async fn branch_protection_update(
        &self,
        namespace: String,
        repo_name: String,
        rule_id: i64,
        request: BranchProtectionUpdateRequest,
        ctx: &Session,
    ) -> Result<BranchProtectionResponse, AppError> {
        let repo = self
            .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
            .await?;
        let rule = repo_branch_protect::Entity::find_by_id(rule_id)
            .filter(repo_branch_protect::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Branch protection rule not found".to_string()))?;
        let mut active: repo_branch_protect::ActiveModel = rule.into();
        if let Some(v) = request.branch {
            active.branch = Set(v);
        }
        if let Some(v) = request.forbid_push {
            active.forbid_push = Set(v);
        }
        if let Some(v) = request.forbid_pull {
            active.forbid_pull = Set(v);
        }
        if let Some(v) = request.forbid_merge {
            active.forbid_merge = Set(v);
        }
        if let Some(v) = request.forbid_deletion {
            active.forbid_deletion = Set(v);
        }
        if let Some(v) = request.forbid_force_push {
            active.forbid_force_push = Set(v);
        }
        if let Some(v) = request.forbid_tag_push {
            active.forbid_tag_push = Set(v);
        }
        if let Some(v) = request.required_approvals {
            active.required_approvals = Set(v);
        }
        if let Some(v) = request.dismiss_stale_reviews {
            active.dismiss_stale_reviews = Set(v);
        }
        if let Some(v) = request.require_linear_history {
            active.require_linear_history = Set(v);
        }
        if let Some(v) = request.allow_fork_syncing {
            active.allow_fork_syncing = Set(v);
        }
        let updated = active.update(&self.db).await?;
        Ok(BranchProtectionResponse::from(updated))
    }

    /// Delete a branch protection rule.
    pub async fn branch_protection_delete(
        &self,
        namespace: String,
        repo_name: String,
        rule_id: i64,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let repo = self
            .utils_check_repo_admin(namespace, repo_name, ctx)
            .await?;
        let deleted = repo_branch_protect::Entity::delete_many()
            .filter(repo_branch_protect::Column::Id.eq(rule_id))
            .filter(repo_branch_protect::Column::Repo.eq(repo.id))
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound(
                "Branch protection rule not found".to_string(),
            ));
        }
        Ok(())
    }

    /// Check approval count for a PR against branch protection required_approvals.
    pub async fn branch_protection_check_approvals(
        &self,
        namespace: String,
        repo_name: String,
        pr_number: i64,
        _ctx: &Session,
    ) -> Result<ApprovalCheckResult, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, _ctx).await?;
        let pr = pr_module::PullRequest::find()
            .filter(pr_module::pull_request::Column::Repo.eq(repo.id))
            .filter(pr_module::pull_request::Column::Number.eq(pr_number))
            .one(&self.db)
            .await?
            .ok_or_else(|| AppError::NotFound("Pull request not found".to_string()))?;

        // Find branch protection for the base branch
        let protection = repo_branch_protect::Entity::find()
            .filter(repo_branch_protect::Column::Repo.eq(repo.id))
            .filter(repo_branch_protect::Column::Branch.eq(&pr.base))
            .one(&self.db)
            .await?;

        let required = protection
            .as_ref()
            .map(|p| p.required_approvals)
            .unwrap_or(0);

        if required <= 0 {
            return Ok(ApprovalCheckResult {
                enough_approvals: true,
                approvals: 0,
                required,
                reviewers: vec![],
            });
        }

        // Count approvals from pull_request_review table
        let reviews = pull_request_review::Entity::find()
            .filter(pull_request_review::Column::Repo.eq(repo.id))
            .filter(pull_request_review::Column::Number.eq(pr_number))
            .filter(pull_request_review::Column::State.eq(ReviewState::Approved.to_string()))
            .all(&self.db)
            .await?;

        let approvals = reviews.len() as i32;
        let reviewers: Vec<ReviewerInfo> = reviews
            .into_iter()
            .map(|r| ReviewerInfo {
                reviewer: r.reviewer,
                state: r.state,
                submitted_at: r.submitted_at,
            })
            .collect();

        Ok(ApprovalCheckResult {
            enough_approvals: approvals >= required,
            approvals,
            required,
            reviewers,
        })
    }

    /// Find the branch protection rule for a given repo+branch.
    pub async fn branch_protection_find(
        &self,
        repo_id: Uuid,
        branch: &str,
    ) -> Result<Option<repo_branch_protect::Model>, AppError> {
        let rule = repo_branch_protect::Entity::find()
            .filter(repo_branch_protect::Column::Repo.eq(repo_id))
            .filter(repo_branch_protect::Column::Branch.eq(branch))
            .one(&self.db)
            .await?;
        Ok(rule)
    }
}
