use crate::AppService;
use crate::error::AppError;
use crate::project::activity::ActivityLogParams;
use chrono::Utc;
use models::projects::{MemberRole, project_members};
use models::pull_request::{ReviewState, pull_request, pull_request_review};
use models::repos::repo;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct ReviewSubmitRequest {
    pub body: Option<String>,
    pub state: String,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct ReviewUpdateRequest {
    pub body: Option<String>,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ReviewResponse {
    pub repo: Uuid,
    pub number: i64,
    pub reviewer: Uuid,
    pub reviewer_username: Option<String>,
    pub state: String,
    pub body: Option<String>,
    pub submitted_at: Option<chrono::DateTime<Utc>>,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
}

impl From<pull_request_review::Model> for ReviewResponse {
    fn from(r: pull_request_review::Model) -> Self {
        Self {
            repo: r.repo,
            number: r.number,
            reviewer: r.reviewer,
            reviewer_username: None,
            state: r.state,
            body: r.body,
            submitted_at: r.submitted_at,
            created_at: r.created_at,
            updated_at: r.updated_at,
        }
    }
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct ReviewListResponse {
    pub reviews: Vec<ReviewResponse>,
}

impl AppService {
    /// List all reviews on a pull request.
    pub async fn review_list(
        &self,
        namespace: String,
        repo_name: String,
        pr_number: i64,
        ctx: &Session,
    ) -> Result<ReviewListResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;

        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))
            .order_by_asc(pull_request_review::Column::SubmittedAt)
            .all(&self.db)
            .await?;

        let reviewer_ids: Vec<Uuid> = reviews.iter().map(|r| r.reviewer).collect();
        let reviewers = if reviewer_ids.is_empty() {
            vec![]
        } else {
            models::users::user::Entity::find()
                .filter(models::users::user::Column::Uid.is_in(reviewer_ids))
                .all(&self.db)
                .await?
        };

        let responses: Vec<ReviewResponse> = reviews
            .into_iter()
            .map(|r| {
                let username = reviewers
                    .iter()
                    .find(|u| u.uid == r.reviewer)
                    .map(|u| u.username.clone());
                ReviewResponse {
                    reviewer_username: username,
                    ..ReviewResponse::from(r)
                }
            })
            .collect();

        Ok(ReviewListResponse { reviews: responses })
    }

    /// Submit a review on a pull request.
    /// Any project member can submit a review.
    /// Updates existing pending review if found, otherwise creates new.
    pub async fn review_submit(
        &self,
        namespace: String,
        repo_name: String,
        pr_number: i64,
        request: ReviewSubmitRequest,
        ctx: &Session,
    ) -> Result<ReviewResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;

        // Verify PR exists
        let pr = pull_request::Entity::find()
            .filter(pull_request::Column::Repo.eq(repo.id))
            .filter(pull_request::Column::Number.eq(pr_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Pull request not found".to_string()))?;

        // Cannot review merged PRs
        if pr.status == models::pull_request::PrStatus::Merged.to_string() {
            return Err(AppError::BadRequest(
                "Cannot review a merged pull request".to_string(),
            ));
        }

        // Parse and validate review state
        let state: ReviewState = request.state.parse().map_err(|_| {
            AppError::BadRequest(
                "Invalid review state, expected: pending, approved, changes_requested, comment"
                    .to_string(),
            )
        })?;

        let now = Utc::now();
        let submitted_at = if state == ReviewState::Pending {
            None
        } else {
            Some(now)
        };

        // Check if reviewer already has a review
        let existing = 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::Reviewer.eq(user_uid))
            .one(&self.db)
            .await?;

        let model = if let Some(existing) = existing {
            let mut active: pull_request_review::ActiveModel = existing.into();
            active.state = Set(state.to_string());
            if let Some(body) = request.body {
                active.body = Set(Some(body));
            }
            active.submitted_at = Set(submitted_at);
            active.updated_at = Set(now);
            active.update(&self.db).await?
        } else {
            let active = pull_request_review::ActiveModel {
                repo: Set(repo.id),
                number: Set(pr_number),
                reviewer: Set(user_uid),
                state: Set(state.to_string()),
                body: Set(request.body),
                submitted_at: Set(submitted_at),
                created_at: Set(now),
                updated_at: Set(now),
                ..Default::default()
            };
            active.insert(&self.db).await?
        };

        super::invalidate_pr_cache(&self.cache, repo.id, pr_number).await;

        let username = models::users::user::Entity::find_by_id(user_uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username);

        let reviewer_name = username.clone().unwrap_or_else(|| user_uid.to_string());
        let _ = self
            .project_log_activity(
                repo.project,
                Some(repo.id),
                user_uid,
                ActivityLogParams {
                    event_type: "pr_review".to_string(),
                    title: format!("{} reviewed PR #{}: {}", reviewer_name, pr_number, state),
                    repo_id: Some(repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: Some(pr_number),
                    metadata: Some(serde_json::json!({
                        "pr_number": pr_number,
                        "review_state": state.to_string(),
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(ReviewResponse {
            reviewer_username: username.clone(),
            ..ReviewResponse::from(model.clone())
        })
    }

    /// Update a review body. Only the reviewer themselves OR admin/owner.
    pub async fn review_update(
        &self,
        namespace: String,
        repo_name: String,
        pr_number: i64,
        request: ReviewUpdateRequest,
        ctx: &Session,
    ) -> Result<ReviewResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;

        let review = 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::Reviewer.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Review not found".to_string()))?;

        // Permission: reviewer OR admin/owner
        let member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(repo.project))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NoPower)?;
        let role = member.scope_role().map_err(|_| AppError::RoleParseError)?;
        let is_reviewer = review.reviewer == user_uid;
        let is_admin = role == MemberRole::Admin || role == MemberRole::Owner;

        if !is_reviewer && !is_admin {
            return Err(AppError::NoPower);
        }

        let mut active: pull_request_review::ActiveModel = review.clone().into();
        if let Some(body) = request.body {
            active.body = Set(Some(body));
        }
        active.updated_at = Set(Utc::now());
        let model = active.update(&self.db).await?;

        super::invalidate_pr_cache(&self.cache, repo.id, pr_number).await;

        let username = models::users::user::Entity::find_by_id(model.reviewer)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username);

        Ok(ReviewResponse {
            reviewer_username: username,
            ..ReviewResponse::from(model)
        })
    }

    /// Delete a review. Only the reviewer themselves OR admin/owner.
    pub async fn review_delete(
        &self,
        namespace: String,
        repo_name: String,
        pr_number: i64,
        reviewer_id: Uuid,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo: repo::Model = self
            .utils_check_repo_admin(namespace.clone(), repo_name.clone(), ctx)
            .await?;

        let review = 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::Reviewer.eq(reviewer_id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Review not found".to_string()))?;

        // Permission: reviewer themselves OR admin/owner
        let is_reviewer = review.reviewer == user_uid;
        if !is_reviewer {
            let member = project_members::Entity::find()
                .filter(project_members::Column::Project.eq(repo.project))
                .filter(project_members::Column::User.eq(user_uid))
                .one(&self.db)
                .await?
                .ok_or(AppError::NoPower)?;
            let role = member.scope_role().map_err(|_| AppError::RoleParseError)?;
            if role != MemberRole::Admin && role != MemberRole::Owner {
                return Err(AppError::NoPower);
            }
        }

        // Cascade delete review comments
        models::pull_request::PullRequestReviewComment::delete_many()
            .filter(models::pull_request::pull_request_review_comment::Column::Repo.eq(repo.id))
            .filter(models::pull_request::pull_request_review_comment::Column::Number.eq(pr_number))
            .filter(
                models::pull_request::pull_request_review_comment::Column::Review.eq(reviewer_id),
            )
            .exec(&self.db)
            .await?;

        pull_request_review::Entity::delete_by_id((repo.id, pr_number, reviewer_id))
            .exec(&self.db)
            .await?;

        super::invalidate_pr_cache(&self.cache, repo.id, pr_number).await;

        Ok(())
    }
}
