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

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

/// Generate and execute conventional commits
#[derive(Parser)]
pub struct CommitCommand {
    /// Commit type
    #[arg(short, 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(short, 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,

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

impl CommitCommand {
    pub async fn execute(&self) -> Result<()> {
        // Find git repository
        let repo = find_repo(std::env::current_dir()?.as_path())?;
        
        // Check for changes
        let status = repo.status_summary()?;
        if status.clean && !self.amend {
            bail!("No changes to commit. Working tree is clean.");
        }

        // Load configuration
        let manager = ConfigManager::new()?;
        let config = manager.config();

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

        // Stage all if requested
        if self.all {
            repo.stage_all()?;
            println!("{}", "✓ Staged all changes".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 && !self.yes {
            // AI-generated commit
            self.generate_commit(&repo, format).await?
        } else {
            // Interactive commit creation
            self.create_interactive_commit(format).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!("{}", "Commit preview:".bold());
            println!("{}", "─".repeat(60));
            println!("{}", commit_message);
            println!("{}", "─".repeat(60));

            let confirm = Confirm::new()
                .with_prompt("Do you want to proceed with this commit?")
                .default(true)
                .interact()?;

            if !confirm {
                println!("{}", "Commit cancelled.".yellow());
                return Ok(());
            }
        }

        let result = if self.amend {
            if self.dry_run {
                println!("\n{}", "Dry run - commit not amended.".yellow());
                return Ok(());
            }
            self.amend_commit(&repo, &commit_message)?;
            None
        } else {
            if self.dry_run {
                println!("\n{}", "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!("{} {}", "✓ Created commit".green().bold(), commit_oid.to_string()[..8].to_string().cyan());
        } else {
            println!("{} {}", "✓ Amended commit".green().bold(), "successfully");
        }

        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 commit_type = self.commit_type.clone()
            .ok_or_else(|| anyhow::anyhow!("Commit type required for manual commit. Use -t <type>"))?;

        let description = self.message.clone()
            .ok_or_else(|| anyhow::anyhow!("Description required for manual commit. Use -m <message>"))?;

        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) -> Result<String> {
        let manager = ConfigManager::new()?;
        let config = manager.config();

        // Check if LLM is configured
        let generator = ContentGenerator::new(&config.llm).await
            .context("Failed to initialize LLM. Use --manual for manual commit.")?;

        println!("{} AI is analyzing your changes...", "🤖".to_string());

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

        Ok(generated.to_conventional())
    }

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

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

        // Enter scope (optional)
        let scope: String = Input::new()
            .with_prompt("Scope (optional, press Enter to skip)")
            .allow_empty(true)
            .interact_text()?;
        let scope = if scope.is_empty() { None } else { Some(scope) };

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

        // Breaking change
        let breaking = Confirm::new()
            .with_prompt("Is this a breaking change?")
            .default(false)
            .interact()?;

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

        let body = if add_body {
            let body_text = crate::utils::editor::edit_content("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 stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to amend commit: {}", stderr);
        }

        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
        }
    }
}
