use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::issues::{issue, issue_repo};
use models::projects::project_members;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct IssueLinkRepoRequest {
    pub repo: Uuid,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueRepoResponse {
    pub issue: Uuid,
    pub repo: Uuid,
    pub relation_at: chrono::DateTime<Utc>,
}

impl AppService {
    /// List repos linked to an issue.
    pub async fn issue_repo_list(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<Vec<IssueRepoResponse>, AppError> {
        let project = self.utils_find_project_by_name(project_name).await?;
        if let Some(uid) = ctx.user() {
            self.check_project_access(project.id, uid).await?;
        }

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        let repos = issue_repo::Entity::find()
            .filter(issue_repo::Column::Issue.eq(issue.id))
            .all(&self.db)
            .await?;

        let responses: Vec<IssueRepoResponse> = repos
            .into_iter()
            .map(|r| IssueRepoResponse {
                issue: r.issue,
                repo: r.repo,
                relation_at: r.relation_at,
            })
            .collect();

        Ok(responses)
    }

    /// Link a repo to an issue.
    pub async fn issue_repo_link(
        &self,
        project_name: String,
        issue_number: i64,
        request: IssueLinkRepoRequest,
        ctx: &Session,
    ) -> Result<IssueRepoResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let _member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NoPower)?;

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        let existing = issue_repo::Entity::find()
            .filter(issue_repo::Column::Issue.eq(issue.id))
            .filter(issue_repo::Column::Repo.eq(request.repo))
            .one(&self.db)
            .await?;
        if existing.is_some() {
            return Err(AppError::BadRequest(
                "Repo already linked to this issue".to_string(),
            ));
        }

        let now = Utc::now();
        let active = issue_repo::ActiveModel {
            issue: Set(issue.id),
            repo: Set(request.repo),
            relation_at: Set(now),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;

        self.invalidate_issue_cache(project.id, issue_number).await;

        Ok(IssueRepoResponse {
            issue: model.issue,
            repo: model.repo,
            relation_at: model.relation_at,
        })
    }

    /// Unlink a repo from an issue.
    pub async fn issue_repo_unlink(
        &self,
        project_name: String,
        issue_number: i64,
        repo_id: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let _member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NoPower)?;

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        issue_repo::Entity::delete_many()
            .filter(issue_repo::Column::Issue.eq(issue.id))
            .filter(issue_repo::Column::Repo.eq(repo_id))
            .exec(&self.db)
            .await?;

        self.invalidate_issue_cache(project.id, issue_number).await;

        Ok(())
    }
}
