//! AI-powered issue triage service.
//!
//! Analyzes newly created issues and suggests labels and priority.

use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use config::AppConfig;
use models::agents::ModelStatus;
use models::agents::model::{Column as MColumn, Entity as MEntity};
use models::issues::{issue, issue_comment};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct IssueTriageSuggestion {
    pub suggested_labels: Vec<String>,
    pub priority: String,
    pub reasoning: String,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueTriageResponse {
    pub suggestions: Option<IssueTriageSuggestion>,
    pub comment_posted: bool,
}

fn build_triage_prompt(title: &str, body: Option<&str>, existing_labels: &[String]) -> String {
    let body_text = body.unwrap_or("(no description)");
    let labels_text = if existing_labels.is_empty() {
        "none".to_string()
    } else {
        existing_labels.join(", ")
    };

    format!(
        r#"You are an expert software project manager. Analyze the following GitHub issue and suggest how to triage it.

Issue Title: {}
Issue Body:
{}
Existing Labels: {}

Based on the issue, suggest:
1. Additional labels from this standard set: bug, enhancement, documentation, question, help wanted, good first issue, priority:high, priority:medium, priority:low, kind:backend, kind:frontend, kind:dx, kind:security, kind:performance
2. A priority level: high, medium, or low
3. A brief reasoning for your assessment

Respond in JSON format like:
{{
  "suggested_labels": ["bug", "priority:high"],
  "priority": "high",
  "reasoning": "This is a critical security vulnerability in the auth module..."
}}

Only suggest labels not already in the existing list. Be concise."#,
        title, body_text, labels_text
    )
}

fn parse_triage_response(content: &str) -> Option<IssueTriageSuggestion> {
    let content = content.trim();
    let json_str = if content.starts_with("```json") {
        content
            .strip_prefix("```json")?
            .strip_prefix('\n')
            .unwrap_or(content)
            .trim_end_matches("```")
            .trim()
    } else if content.starts_with("```") {
        content
            .strip_prefix("```")?
            .strip_prefix('\n')
            .unwrap_or(content)
            .trim_end_matches("```")
            .trim()
    } else {
        content
    };

    let parsed: serde_json::Value = serde_json::from_str(json_str).ok()?;
    Some(IssueTriageSuggestion {
        suggested_labels: parsed
            .get("suggested_labels")?
            .as_array()?
            .iter()
            .filter_map(|v| v.as_str().map(String::from))
            .collect(),
        priority: parsed.get("priority")?.as_str()?.to_string(),
        reasoning: parsed.get("reasoning")?.as_str()?.to_string(),
    })
}

async fn call_ai_for_triage(
    model_name: &str,
    prompt: &str,
    app_config: &AppConfig,
) -> Result<String, AppError> {
    let api_key = app_config
        .ai_api_key()
        .map_err(|e| AppError::InternalServerError(format!("AI API key not configured: {}", e)))?;

    let base_url = app_config
        .ai_basic_url()
        .unwrap_or_else(|_| "https://api.openai.com".into());

    let client_config =
        ::agent::AiClientConfig::new(api_key).with_base_url(base_url);

    let messages = vec![agent::ChatRequestMessage::user(prompt.to_string())];

    let response = ::agent::call_with_params(
        &messages,
        model_name,
        &client_config,
        0.3,
        1024,
        None,
        None,
        None,
    )
    .await
    .map_err(|e| {
        AppError::InternalServerError(format!("AI triage call failed: {}", e))
    })?;

    Ok(response.content)
}

impl AppService {
    /// Run AI triage on a newly created issue and post a suggestion comment.
    /// Called asynchronously after issue creation.
    pub async fn triage_issue(
        &self,
        project_name: String,
        issue_number: i64,
    ) -> Result<IssueTriageResponse, AppError> {
        let project = self.utils_find_project_by_name(project_name.clone()).await?;

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

        let existing_labels: Vec<String> = Vec::new();

        let model = match MEntity::find()
            .filter(MColumn::Status.eq(ModelStatus::Active.to_string()))
            .order_by_asc(MColumn::Name)
            .one(&self.db)
            .await?
        {
            Some(m) => m,
            None => {
                tracing::debug!(
                    project = %project_name,
                    issue = issue_number,
                    "No active AI model for triage — skipping"
                );
                return Ok(IssueTriageResponse {
                    suggestions: None,
                    comment_posted: false,
                });
            }
        };

        let prompt =
            build_triage_prompt(&issue_model.title, issue_model.body.as_deref(), &existing_labels);
        let ai_content = match call_ai_for_triage(&model.name, &prompt, &self.config).await {
            Ok(c) => c,
            Err(e) => {
                tracing::warn!(
                    project = %project_name,
                    issue = issue_number,
                    error = ?e,
                    "AI triage failed"
                );
                return Ok(IssueTriageResponse {
                    suggestions: None,
                    comment_posted: false,
                });
            }
        };

        let suggestions = parse_triage_response(&ai_content);
        let mut comment_posted = false;

        if let Some(ref s) = suggestions {
            let comment_body = format!(
                "## AI Triage Suggestions\n\n**Priority:** *{}*\n\n{}\n\n**Suggested Labels:** \
                 {}\n\n_This analysis was generated automatically by the AI collaborator._",
                s.priority.to_uppercase(),
                s.reasoning,
                if s.suggested_labels.is_empty() {
                    "none".to_string()
                } else {
                    s.suggested_labels.join(", ")
                }
            );

            let now = Utc::now();
            let active = issue_comment::ActiveModel {
                issue: Set(issue_model.id),
                author: Set(Uuid::nil()),
                body: Set(comment_body),
                created_at: Set(now),
                updated_at: Set(now),
                ..Default::default()
            };
            if active.insert(&self.db).await.is_ok() {
                comment_posted = true;
            }
        }

        Ok(IssueTriageResponse {
            suggestions,
            comment_posted,
        })
    }
}
