use crate::config::{CommitFormat, Language};
use crate::config::manager::ConfigManager;
use crate::git::{CommitInfo, GitRepo};
use crate::llm::{GeneratedCommit, LlmClient};
use anyhow::{Context, Result};

/// Content generator using LLM
pub struct ContentGenerator {
    llm_client: LlmClient,
}

impl ContentGenerator {
    /// Create new content generator
    pub async fn new(manager: &ConfigManager) -> Result<Self> {
        let llm_client = LlmClient::from_config(manager).await?;
        
        if !llm_client.is_available().await {
            anyhow::bail!("LLM provider '{}' is not available", manager.llm_provider());
        }
        
        Ok(Self { llm_client })
    }

    /// Generate commit message from diff
    pub async fn generate_commit_message(
        &self,
        diff: &str,
        format: CommitFormat,
        language: Language,
    ) -> Result<GeneratedCommit> {
        // Truncate diff if too long
        let max_diff_len = 4000;
        let truncated_diff = if diff.len() > max_diff_len {
            let boundary = diff.floor_char_boundary(max_diff_len);
            format!("{}\n... (truncated)", &diff[..boundary])
        } else {
            diff.to_string()
        };
        
        self.llm_client.generate_commit_message(&truncated_diff, format, language).await
    }

    /// Generate commit message from repository changes
    pub async fn generate_commit_from_repo(
        &self,
        repo: &GitRepo,
        format: CommitFormat,
        language: Language,
    ) -> Result<GeneratedCommit> {
        let diff = repo.get_staged_diff_sorted()
            .context("Failed to get staged diff")?;
        
        if diff.is_empty() {
            anyhow::bail!("No staged changes to generate commit from");
        }
        
        self.generate_commit_message(&diff, format, language).await
    }

    /// Generate tag message
    pub async fn generate_tag_message(
        &self,
        version: &str,
        commits: &[CommitInfo],
        language: Language,
    ) -> Result<String> {
        let commit_messages: Vec<String> = commits
            .iter()
            .map(|c| c.subject().to_string())
            .collect();
        
        self.llm_client.generate_tag_message(version, &commit_messages, language).await
    }

    /// Generate changelog entry
    pub async fn generate_changelog_entry(
        &self,
        version: &str,
        commits: &[CommitInfo],
        language: Language,
    ) -> Result<String> {
        let typed_commits: Vec<(String, String)> = commits
            .iter()
            .map(|c| {
                let commit_type = c.commit_type().unwrap_or_else(|| "other".to_string());
                (commit_type, c.subject().to_string())
            })
            .collect();
        
        self.llm_client.generate_changelog_entry(version, &typed_commits, language).await
    }

    /// Generate changelog from repository
    pub async fn generate_changelog_from_repo(
        &self,
        repo: &GitRepo,
        version: &str,
        from_tag: Option<&str>,
        language: Language,
    ) -> Result<String> {
        let commits = if let Some(tag) = from_tag {
            repo.get_commits_between(tag, "HEAD")?
        } else {
            repo.get_commits(50)?
        };
        
        self.generate_changelog_entry(version, &commits, language).await
    }

    /// Interactive commit generation with user feedback
    pub async fn generate_commit_interactive(
        &self,
        repo: &GitRepo,
        format: CommitFormat,
        language: Language,
    ) -> Result<GeneratedCommit> {
        use dialoguer::Select;
        
        let diff = repo.get_staged_diff_sorted()?;
        
        if diff.is_empty() {
            anyhow::bail!("No staged changes");
        }
        
        // Show diff summary
        let files = repo.get_staged_files()?;
        println!("\nStaged files ({}):", files.len());
        for file in &files {
            println!("  • {}", file);
        }
        
        // Generate initial commit
        println!("\nGenerating commit message...");
        let mut generated = self.generate_commit_message(&diff, format, language).await?;
        
        loop {
            println!("\n{}", "─".repeat(60));
            println!("Generated commit message:");
            println!("{}", "─".repeat(60));
            println!("{}", generated.to_conventional());
            println!("{}", "─".repeat(60));
            
            let options = vec![
                "✓ Accept and commit",
                "🔄 Regenerate",
                "✏️  Edit",
                "❌ Cancel",
            ];
            
            let selection = Select::new()
                .with_prompt("What would you like to do?")
                .items(&options)
                .default(0)
                .interact()?;
            
            match selection {
                0 => return Ok(generated),
                1 => {
                    println!("Regenerating...");
                    generated = self.generate_commit_message(&diff, format, language).await?;
                }
                2 => {
                    let edited = crate::utils::editor::edit_content(&generated.to_conventional())?;
                    generated = self.parse_edited_commit(&edited, format)?;
                }
                3 => anyhow::bail!("Cancelled by user"),
                _ => {}
            }
        }
    }

    fn parse_edited_commit(&self, edited: &str, _format: CommitFormat) -> Result<GeneratedCommit> {
        let parsed = crate::git::commit::parse_commit_message(edited);
        
        Ok(GeneratedCommit {
            commit_type: parsed.commit_type.unwrap_or_else(|| "chore".to_string()),
            scope: parsed.scope,
            description: parsed.description.unwrap_or_else(|| "update".to_string()),
            body: parsed.body,
            footer: parsed.footer,
            breaking: parsed.breaking,
        })
    }
}

/// Fallback generators when LLM is not available
pub mod fallback {
    use crate::git::commit::create_date_commit_message;

    /// Generate simple commit message without LLM
    pub fn generate_simple_commit(files: &[String]) -> String {
        if files.len() == 1 {
            format!("chore: update {}", files[0])
        } else if files.len() <= 3 {
            format!("chore: update {}", files.join(", "))
        } else {
            format!("chore: update {} files", files.len())
        }
    }

    /// Generate date-based commit
    pub fn generate_date_commit() -> String {
        create_date_commit_message(None)
    }

    /// Generate commit based on file types
    pub fn generate_by_file_types(files: &[String]) -> String {
        let has_code = files.iter().any(|f| {
            f.ends_with(".rs") || f.ends_with(".py") || f.ends_with(".js") || f.ends_with(".ts")
        });
        
        let has_docs = files.iter().any(|f| f.ends_with(".md") || f.contains("README"));
        
        let has_tests = files.iter().any(|f| f.contains("test") || f.contains("spec"));
        
        if has_tests {
            "test: update tests".to_string()
        } else if has_docs {
            "docs: update documentation".to_string()
        } else if has_code {
            "refactor: update code".to_string()
        } else {
            "chore: update files".to_string()
        }
    }
}
