use super::GitRepo;
use anyhow::{Result, bail};
use chrono::Local;

/// Commit builder for creating commits
pub struct CommitBuilder {
    commit_type: Option<String>,
    scope: Option<String>,
    description: Option<String>,
    body: Option<String>,
    footer: Option<String>,
    message: Option<String>,
    breaking: bool,
    sign: bool,
    amend: bool,
    no_verify: bool,
    format: crate::config::CommitFormat,
}

impl CommitBuilder {
    /// Create new commit builder
    pub fn new() -> Self {
        Self {
            commit_type: None,
            scope: None,
            description: None,
            body: None,
            footer: None,
            message: None,
            breaking: false,
            sign: false,
            amend: false,
            no_verify: false,
            format: crate::config::CommitFormat::Conventional,
        }
    }

    /// Set commit type
    pub fn commit_type(mut self, commit_type: impl Into<String>) -> Self {
        self.commit_type = Some(commit_type.into());
        self
    }

    /// Set scope
    pub fn scope(mut self, scope: impl Into<String>) -> Self {
        self.scope = Some(scope.into());
        self
    }

    /// Set scope (optional)
    pub fn scope_opt(mut self, scope: Option<String>) -> Self {
        self.scope = scope;
        self
    }

    /// Set description
    pub fn description(mut self, description: impl Into<String>) -> Self {
        self.description = Some(description.into());
        self
    }

    /// Set body
    pub fn body(mut self, body: impl Into<String>) -> Self {
        self.body = Some(body.into());
        self
    }

    /// Set body (optional)
    pub fn body_opt(mut self, body: Option<String>) -> Self {
        self.body = body;
        self
    }

    /// Set footer
    pub fn footer(mut self, footer: impl Into<String>) -> Self {
        self.footer = Some(footer.into());
        self
    }

    /// Set message
    pub fn message(mut self, message: impl Into<String>) -> Self {
        self.message = Some(message.into());
        self
    }

    /// Mark as breaking change
    pub fn breaking(mut self, breaking: bool) -> Self {
        self.breaking = breaking;
        self
    }

    /// Sign the commit
    pub fn sign(mut self, sign: bool) -> Self {
        self.sign = sign;
        self
    }

    /// Amend previous commit
    pub fn amend(mut self, amend: bool) -> Self {
        self.amend = amend;
        self
    }

    /// Skip pre-commit hooks
    pub fn no_verify(mut self, no_verify: bool) -> Self {
        self.no_verify = no_verify;
        self
    }

    /// Set commit format
    pub fn format(mut self, format: crate::config::CommitFormat) -> Self {
        self.format = format;
        self
    }

    /// Build commit message
    pub fn build_message(&self) -> Result<String> {
        if let Some(ref msg) = self.message {
            return Ok(msg.clone());
        }

        let commit_type = self
            .commit_type
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Commit type is required"))?;

        let description = self
            .description
            .as_ref()
            .ok_or_else(|| anyhow::anyhow!("Description is required"))?;

        let message = match self.format {
            crate::config::CommitFormat::Conventional => {
                crate::utils::formatter::format_conventional_commit(
                    commit_type,
                    self.scope.as_deref(),
                    description,
                    self.body.as_deref(),
                    self.footer.as_deref(),
                    self.breaking,
                )
            }
            crate::config::CommitFormat::Commitlint => {
                crate::utils::formatter::format_commitlint_commit(
                    commit_type,
                    self.scope.as_deref(),
                    description,
                    self.body.as_deref(),
                    self.footer.as_deref(),
                    None,
                )
            }
        };

        Ok(message)
    }

    /// Execute commit
    pub fn execute(&self, repo: &GitRepo) -> Result<Option<String>> {
        let message = self.build_message()?;

        if self.amend {
            self.amend_commit(repo, &message)?;
            Ok(None)
        } else {
            repo.commit(&message, self.sign)?;
            Ok(None)
        }
    }

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

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

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

        args.push("-m");
        args.push(message);

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

impl Default for CommitBuilder {
    fn default() -> Self {
        Self::new()
    }
}

/// Create a date-based commit message
pub fn create_date_commit_message(prefix: Option<&str>) -> String {
    let now = Local::now();
    let date_str = now.format("%Y-%m-%d").to_string();

    match prefix {
        Some(p) => format!("{}: {}", p, date_str),
        None => format!("chore: update {}", date_str),
    }
}

/// Commit type suggestions based on diff
pub fn suggest_commit_type(diff: &str) -> Vec<&'static str> {
    let mut suggestions = vec![];

    // Check for test files
    if diff.contains("test") || diff.contains("spec") || diff.contains("__tests__") {
        suggestions.push("test");
    }

    // Check for documentation
    if diff.contains("README") || diff.contains(".md") || diff.contains("docs/") {
        suggestions.push("docs");
    }

    // Check for configuration files
    if diff.contains("config")
        || diff.contains(".json")
        || diff.contains(".yaml")
        || diff.contains(".toml")
    {
        suggestions.push("chore");
    }

    // Check for dependencies
    if diff.contains("Cargo.toml")
        || diff.contains("package.json")
        || diff.contains("requirements.txt")
    {
        suggestions.push("build");
    }

    // Check for CI
    if diff.contains(".github/") || diff.contains(".gitlab-") || diff.contains("Jenkinsfile") {
        suggestions.push("ci");
    }

    // Default suggestions
    if suggestions.is_empty() {
        suggestions.extend(&["feat", "fix", "refactor"]);
    }

    suggestions
}

/// Parse existing commit message
pub fn parse_commit_message(message: &str) -> ParsedCommit {
    let lines: Vec<&str> = message.lines().collect();

    if lines.is_empty() {
        return ParsedCommit::default();
    }

    let first_line = lines[0];

    // Try to parse as conventional commit
    if let Some(colon_pos) = first_line.find(':') {
        let type_part = &first_line[..colon_pos];
        let description = first_line[colon_pos + 1..].trim();

        let breaking = type_part.ends_with('!');
        let type_part = type_part.trim_end_matches('!');

        let (commit_type, scope) = if let Some(open) = type_part.find('(') {
            if let Some(close) = type_part.find(')') {
                let t = &type_part[..open];
                let s = &type_part[open + 1..close];
                (Some(t.to_string()), Some(s.to_string()))
            } else {
                (Some(type_part.to_string()), None)
            }
        } else {
            (Some(type_part.to_string()), None)
        };

        // Extract body and footer
        let mut body_lines = vec![];
        let mut footer_lines = vec![];
        let mut in_footer = false;

        for line in &lines[1..] {
            if line.trim().is_empty() {
                continue;
            }

            if line.starts_with("BREAKING CHANGE:")
                || line.starts_with("Closes")
                || line.starts_with("Fixes")
                || line.starts_with("Refs")
                || line.starts_with("Co-authored-by:")
            {
                in_footer = true;
            }

            if in_footer {
                footer_lines.push(line.to_string());
            } else {
                body_lines.push(line.to_string());
            }
        }

        return ParsedCommit {
            commit_type,
            scope,
            description: Some(description.to_string()),
            body: if body_lines.is_empty() {
                None
            } else {
                Some(body_lines.join("\n"))
            },
            footer: if footer_lines.is_empty() {
                None
            } else {
                Some(footer_lines.join("\n"))
            },
            breaking,
        };
    }

    // Non-conventional commit
    ParsedCommit {
        description: Some(first_line.to_string()),
        ..Default::default()
    }
}

/// Parsed commit structure
#[derive(Debug, Clone, Default)]
pub struct ParsedCommit {
    pub commit_type: Option<String>,
    pub scope: Option<String>,
    pub description: Option<String>,
    pub body: Option<String>,
    pub footer: Option<String>,
    pub breaking: bool,
}

impl ParsedCommit {
    /// Convert back to commit message
    pub fn to_message(&self, format: crate::config::CommitFormat) -> String {
        let commit_type = self.commit_type.as_deref().unwrap_or("chore");
        let description = self.description.as_deref().unwrap_or("update");

        match format {
            crate::config::CommitFormat::Conventional => {
                crate::utils::formatter::format_conventional_commit(
                    commit_type,
                    self.scope.as_deref(),
                    description,
                    self.body.as_deref(),
                    self.footer.as_deref(),
                    self.breaking,
                )
            }
            crate::config::CommitFormat::Commitlint => {
                crate::utils::formatter::format_commitlint_commit(
                    commit_type,
                    self.scope.as_deref(),
                    description,
                    self.body.as_deref(),
                    self.footer.as_deref(),
                    None,
                )
            }
        }
    }
}
