use anyhow::{Context, Result, bail};
use clap::Parser;
use colored::Colorize;
use dialoguer::{Confirm, Input, Select};
use std::path::PathBuf;

use crate::config::CommitFormat;
use crate::config::{Language, manager::ConfigManager};
use crate::generator::ContentGenerator;
use crate::git::commit::{CommitBuilder, create_date_commit_message};
use crate::git::{GitRepo, find_repo};
use crate::i18n::Messages;
use crate::utils::validators::get_commit_types;

/// Generate and execute conventional commits
#[derive(Parser)]
pub struct CommitCommand {
    /// Commit type
    #[arg(long)]
    commit_type: Option<String>,

    /// Commit scope
    #[arg(short, long)]
    scope: Option<String>,

    /// Commit description/subject
    #[arg(short, long)]
    message: Option<String>,

    /// Commit body
    #[arg(long)]
    body: Option<String>,

    /// Mark as breaking change
    #[arg(short, long)]
    breaking: bool,

    /// Use date-based commit message
    #[arg(short, long)]
    date: bool,

    /// Manual input (skip AI generation)
    #[arg(long)]
    manual: bool,

    /// Sign the commit
    #[arg(short = 'S', long)]
    sign: bool,

    /// Amend previous commit
    #[arg(long)]
    amend: bool,

    /// Stage all changes before committing
    #[arg(short = 'a', long)]
    all: bool,

    /// Dry run (show message without committing)
    #[arg(long)]
    dry_run: bool,

    /// Use conventional commit format
    #[arg(long, group = "format")]
    conventional: bool,

    /// Use commitlint format
    #[arg(long, group = "format")]
    commitlint: bool,

    /// Don't verify commit message
    #[arg(long)]
    no_verify: bool,

    /// Enable thinking mode for this commit (override config)
    #[arg(short = 't', long)]
    think: bool,

    /// Skip interactive prompts
    #[arg(short = 'y', long)]
    yes: bool,

    /// Push after committing
    #[arg(long)]
    push: bool,

    /// Remote to push to
    #[arg(long, default_value = "origin")]
    remote: String,
}

impl CommitCommand {
    pub async fn execute(&self, config_path: Option<PathBuf>) -> Result<()> {
        // Find git repository
        let repo = find_repo(std::env::current_dir()?.as_path())?;

        // Load configuration
        let manager = if let Some(ref path) = config_path {
            ConfigManager::with_path(path)?
        } else {
            ConfigManager::new()?
        };
        let config = manager.config();
        let language = manager.get_language().unwrap_or(Language::English);
        let messages = Messages::new(language);

        // Check for changes
        let status = repo.status_summary()?;
        if status.clean && !self.amend {
            bail!("{}", messages.no_changes());
        }

        // Determine commit format
        let format = if self.conventional {
            CommitFormat::Conventional
        } else if self.commitlint {
            CommitFormat::Commitlint
        } else {
            config.commit.format
        };

        // Auto-add if no files are staged and there are unstaged/untracked changes
        if status.staged == 0 && (status.unstaged > 0 || status.untracked > 0) && !self.all {
            println!("{}", messages.auto_stage_changes().yellow());
            repo.stage_all()?;
            println!("{}", messages.staged_all().green());

            // Re-check status after staging to ensure changes are detected
            let new_status = repo.status_summary()?;
            if new_status.staged == 0 {
                bail!("Failed to stage changes. Please try running 'git add -A' manually.");
            }
        }

        // Stage all if requested
        if self.all {
            repo.stage_all()?;
            println!("{}", messages.staged_all().green());
        }

        // Generate or build commit message
        let commit_message = if self.date {
            // Date-based commit
            self.create_date_commit()
        } else if self.manual || self.message.is_some() {
            // Manual commit
            self.create_manual_commit(format)?
        } else if config.commit.auto_generate {
            // AI-generated commit
            self.generate_commit(&repo, format, &messages).await?
        } else {
            // Interactive commit creation
            self.create_interactive_commit(format, &messages).await?
        };

        // Validate message
        match format {
            CommitFormat::Conventional => {
                crate::utils::validators::validate_conventional_commit(&commit_message)?;
            }
            CommitFormat::Commitlint => {
                crate::utils::validators::validate_commitlint_commit(&commit_message)?;
            }
        }

        // Show commit preview
        if !self.yes {
            println!("\n{}", "─".repeat(60));
            println!("{}", messages.commit_preview().bold());
            println!("{}", "─".repeat(60));
            println!("{}", commit_message);
            println!("{}", "─".repeat(60));

            let confirm = Confirm::new()
                .with_prompt(messages.proceed_commit())
                .default(true)
                .interact()?;

            if !confirm {
                println!("{}", messages.commit_cancelled().yellow());
                return Ok(());
            }
        }

        let result = if self.amend {
            if self.dry_run {
                println!(
                    "\n{} {}",
                    messages.dry_run(),
                    "- commit not amended.".yellow()
                );
                return Ok(());
            }
            self.amend_commit(&repo, &commit_message)?;
            None
        } else {
            if self.dry_run {
                println!(
                    "\n{} {}",
                    messages.dry_run(),
                    "- commit not created.".yellow()
                );
                return Ok(());
            }
            CommitBuilder::new()
                .message(&commit_message)
                .sign(self.sign)
                .execute(&repo)?
        };

        if let Some(commit_oid) = result {
            println!(
                "{} {}",
                messages.commit_created().green().bold(),
                commit_oid.to_string()[..8].to_string().cyan()
            );
        } else {
            println!("{} successfully", messages.commit_amended().green().bold());
        }

        // Push after commit if requested or ask user
        if self.push {
            println!("\n{}", messages.pushing_commit(&self.remote));
            repo.push(&self.remote, "HEAD")?;
            println!("{}", messages.pushed_commit(&self.remote));
        } else if !self.yes && !self.dry_run {
            let should_push = Confirm::new()
                .with_prompt(messages.push_after_commit())
                .default(false)
                .interact()?;

            if should_push {
                println!("\n{}", messages.pushing_commit(&self.remote));
                repo.push(&self.remote, "HEAD")?;
                println!("{}", messages.pushed_commit(&self.remote));
            }
        }

        Ok(())
    }

    fn create_date_commit(&self) -> String {
        let prefix = self.commit_type.as_deref();
        create_date_commit_message(prefix)
    }

    fn create_manual_commit(&self, format: CommitFormat) -> Result<String> {
        let description = self.message.clone().ok_or_else(|| {
            anyhow::anyhow!("Description required for manual commit. Use -m <message>")
        })?;

        // Try to extract commit type from message if not provided
        let commit_type = if let Some(ref ct) = self.commit_type {
            ct.clone()
        } else {
            // Parse from conventional commit format: "type: description"
            description
                .split(':')
                .next()
                .unwrap_or("feat")
                .trim()
                .to_string()
        };

        let builder = CommitBuilder::new()
            .commit_type(commit_type)
            .description(description)
            .scope_opt(self.scope.clone())
            .body_opt(self.body.clone())
            .breaking(self.breaking)
            .format(format);

        builder.build_message()
    }

    async fn generate_commit(
        &self,
        repo: &GitRepo,
        format: CommitFormat,
        messages: &Messages,
    ) -> Result<String> {
        let manager = ConfigManager::new()?;

        let template = manager
            .default_profile()
            .and_then(|p| p.commit_template().map(|t| t.to_string()));

        let generator = ContentGenerator::new_with_think(&manager, self.think, template)
            .await
            .context("Failed to initialize LLM. Use --manual for manual commit.")?;

        println!("{}", messages.ai_analyzing());

        let language = manager.get_language().unwrap_or(Language::English);

        let generated = if self.yes {
            generator
                .generate_commit_from_repo(repo, format, language)
                .await?
        } else {
            generator
                .generate_commit_interactive(repo, format, language)
                .await?
        };

        Ok(generated.to_conventional())
    }

    async fn create_interactive_commit(
        &self,
        format: CommitFormat,
        messages: &Messages,
    ) -> Result<String> {
        let types = get_commit_types(format == CommitFormat::Commitlint);

        // Select type
        let type_idx = Select::new()
            .with_prompt(messages.select_commit_type())
            .items(types)
            .interact()?;
        let commit_type = types[type_idx].to_string();

        // Enter scope (optional)
        let scope: String = Input::new()
            .with_prompt(messages.scope_optional())
            .allow_empty(true)
            .interact_text()?;
        let scope = if scope.is_empty() { None } else { Some(scope) };

        // Enter description
        let description: String = Input::new()
            .with_prompt(messages.description())
            .interact_text()?;

        // Breaking change
        let breaking = Confirm::new()
            .with_prompt(messages.breaking_change())
            .default(false)
            .interact()?;

        // Add body
        let add_body = Confirm::new()
            .with_prompt(messages.add_body())
            .default(false)
            .interact()?;

        let body = if add_body {
            let body_text = crate::utils::editor::edit_content(messages.enter_commit_body())?;
            if body_text.trim().is_empty() {
                None
            } else {
                Some(body_text)
            }
        } else {
            None
        };

        // Build commit
        let builder = CommitBuilder::new()
            .commit_type(commit_type)
            .description(description)
            .scope_opt(scope)
            .body_opt(body)
            .breaking(breaking)
            .format(format);

        builder.build_message()
    }

    fn amend_commit(&self, repo: &GitRepo, message: &str) -> Result<()> {
        use std::process::Command;

        let mut args = vec!["commit", "--amend", "-m", message];

        if self.no_verify {
            args.push("--no-verify");
        }

        if self.sign {
            args.push("-S");
        }

        let output = Command::new("git")
            .args(&args)
            .current_dir(repo.path())
            .output()?;

        if !output.status.success() {
            let stdout = String::from_utf8_lossy(&output.stdout);
            let stderr = String::from_utf8_lossy(&output.stderr);

            let error_msg = if stderr.is_empty() {
                if stdout.is_empty() {
                    "GPG signing failed. Please check:\n\
                     1. GPG signing key is configured (git config --get user.signingkey)\n\
                     2. GPG agent is running\n\
                     3. You can sign commits manually (try: git commit --amend -S)"
                        .to_string()
                } else {
                    stdout.to_string()
                }
            } else {
                stderr.to_string()
            };

            bail!("Failed to amend commit: {}", error_msg);
        }

        Ok(())
    }
}

// // Helper trait for optional builder methods
// trait CommitBuilderExt {
//     fn scope_opt(self, scope: Option<String>) -> Self;
//     fn body_opt(self, body: Option<String>) -> Self;
// }

// impl CommitBuilderExt for CommitBuilder {
//     fn scope_opt(self, scope: Option<String>) -> Self {
//         if let Some(s) = scope {
//             self.scope(s)
//         } else {
//             self
//         }
//     }

//     fn body_opt(self, body: Option<String>) -> Self {
//         if let Some(b) = body {
//             self.body(b)
//         } else {
//             self
//         }
//     }
// }
