use anyhow::{bail, Result};
use lazy_static::lazy_static;
use regex::Regex;

/// Conventional commit types
pub const CONVENTIONAL_TYPES: &[&str] = &[
    "feat",     // A new feature
    "fix",      // A bug fix
    "docs",     // Documentation only changes
    "style",    // Changes that do not affect the meaning of the code
    "refactor", // A code change that neither fixes a bug nor adds a feature
    "perf",     // A code change that improves performance
    "test",     // Adding missing tests or correcting existing tests
    "build",    // Changes that affect the build system or external dependencies
    "ci",       // Changes to CI configuration files and scripts
    "chore",    // Other changes that don't modify src or test files
    "revert",   // Reverts a previous commit
];

/// Commitlint configuration types (extends conventional)
pub const COMMITLINT_TYPES: &[&str] = &[
    "feat",     // A new feature
    "fix",      // A bug fix
    "docs",     // Documentation only changes
    "style",    // Changes that do not affect the meaning of the code
    "refactor", // A code change that neither fixes a bug nor adds a feature
    "perf",     // A code change that improves performance
    "test",     // Adding missing tests or correcting existing tests
    "build",    // Changes that affect the build system or external dependencies
    "ci",       // Changes to CI configuration files and scripts
    "chore",    // Other changes that don't modify src or test files
    "revert",   // Reverts a previous commit
    "wip",      // Work in progress
    "init",     // Initial commit
    "update",   // Update existing functionality
    "remove",   // Remove functionality
    "security", // Security-related changes
];

lazy_static! {
    /// Regex for conventional commit format
    static ref CONVENTIONAL_COMMIT_REGEX: Regex = Regex::new(
        r"^(?P<type>feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(?:\((?P<scope>[^)]+)\))?(?P<breaking>!)?: (?P<description>.+)$"
    ).unwrap();

    /// Regex for scope validation
    static ref SCOPE_REGEX: Regex = Regex::new(
        r"^[a-z0-9-]+$"
    ).unwrap();

    /// Regex for version tag validation (semver)
    static ref SEMVER_REGEX: Regex = Regex::new(
        r"^v?(?P<major>0|[1-9]\d*)\.(?P<minor>0|[1-9]\d*)\.(?P<patch>0|[1-9]\d*)(?:-(?P<prerelease>(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+(?P<buildmetadata>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$"
    ).unwrap();

    /// Regex for email validation
    static ref EMAIL_REGEX: Regex = Regex::new(
        r"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$"
    ).unwrap();

    /// Regex for SSH key validation (basic)
    static ref SSH_KEY_REGEX: Regex = Regex::new(
        r"^(ssh-rsa|ssh-ed25519|ecdsa-sha2-nistp256|ecdsa-sha2-nistp384|ecdsa-sha2-nistp521)\s+[A-Za-z0-9+/]+={0,2}\s+.*$"
    ).unwrap();

    /// Regex for GPG key ID validation
    static ref GPG_KEY_ID_REGEX: Regex = Regex::new(
        r"^[A-F0-9]{16,40}$"
    ).unwrap();
}

/// Validate conventional commit message
pub fn validate_conventional_commit(message: &str) -> Result<()> {
    let first_line = message.lines().next().unwrap_or("");
    
    if !CONVENTIONAL_COMMIT_REGEX.is_match(first_line) {
        bail!(
            "Invalid conventional commit format. Expected: <type>[optional scope]: <description>\n\
             Valid types: {}",
            CONVENTIONAL_TYPES.join(", ")
        );
    }
    
    // Check description length (max 100 chars for first line)
    if first_line.len() > 100 {
        bail!("Commit subject too long (max 100 characters)");
    }
    
    Ok(())
}

/// Validate @commitlint commit message
pub fn validate_commitlint_commit(message: &str) -> Result<()> {
    let first_line = message.lines().next().unwrap_or("");
    
    // Commitlint is more lenient but still requires type prefix
    let parts: Vec<&str> = first_line.splitn(2, ':').collect();
    if parts.len() != 2 {
        bail!("Invalid commit format. Expected: <type>[optional scope]: <subject>");
    }
    
    let type_part = parts[0];
    let subject = parts[1].trim();
    
    // Extract type (handle scope and breaking indicator)
    let commit_type = type_part
        .split('(')
        .next()
        .unwrap_or("")
        .trim_end_matches('!');
    
    if !COMMITLINT_TYPES.contains(&commit_type) {
        bail!(
            "Invalid commit type: '{}'. Valid types: {}",
            commit_type,
            COMMITLINT_TYPES.join(", ")
        );
    }
    
    // Validate subject
    if subject.is_empty() {
        bail!("Commit subject cannot be empty");
    }
    
    if subject.len() < 4 {
        bail!("Commit subject too short (min 4 characters)");
    }
    
    if subject.len() > 100 {
        bail!("Commit subject too long (max 100 characters)");
    }
    
    // Subject should not start with uppercase
    if subject.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
        bail!("Commit subject should not start with uppercase letter");
    }
    
    // Subject should not end with period
    if subject.ends_with('.') {
        bail!("Commit subject should not end with a period");
    }
    
    Ok(())
}

/// Validate scope name
pub fn validate_scope(scope: &str) -> Result<()> {
    if scope.is_empty() {
        bail!("Scope cannot be empty");
    }
    
    if !SCOPE_REGEX.is_match(scope) {
        bail!("Invalid scope format. Use lowercase letters, numbers, and hyphens only");
    }
    
    Ok(())
}

/// Validate semantic version tag
pub fn validate_semver(version: &str) -> Result<()> {
    let version = version.trim_start_matches('v');
    
    if !SEMVER_REGEX.is_match(version) {
        bail!(
            "Invalid semantic version format. Expected: MAJOR.MINOR.PATCH[-prerelease][+build]\n\
             Examples: 1.0.0, 1.2.3-beta, v2.0.0+build123"
        );
    }
    
    Ok(())
}

/// Validate email address
pub fn validate_email(email: &str) -> Result<()> {
    if !EMAIL_REGEX.is_match(email) {
        bail!("Invalid email address format");
    }
    
    Ok(())
}

/// Validate SSH key format
pub fn validate_ssh_key(key: &str) -> Result<()> {
    if !SSH_KEY_REGEX.is_match(key.trim()) {
        bail!("Invalid SSH public key format");
    }
    
    Ok(())
}

/// Validate GPG key ID
pub fn validate_gpg_key_id(key_id: &str) -> Result<()> {
    if !GPG_KEY_ID_REGEX.is_match(key_id) {
        bail!("Invalid GPG key ID format. Expected 16-40 hexadecimal characters");
    }
    
    Ok(())
}

/// Validate profile name
pub fn validate_profile_name(name: &str) -> Result<()> {
    if name.is_empty() {
        bail!("Profile name cannot be empty");
    }
    
    if name.len() > 50 {
        bail!("Profile name too long (max 50 characters)");
    }
    
    if !name.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') {
        bail!("Profile name can only contain letters, numbers, hyphens, and underscores");
    }
    
    Ok(())
}

/// Check if commit type is valid
pub fn is_valid_commit_type(commit_type: &str, use_commitlint: bool) -> bool {
    let types = if use_commitlint {
        COMMITLINT_TYPES
    } else {
        CONVENTIONAL_TYPES
    };
    
    types.contains(&commit_type)
}

/// Get available commit types
pub fn get_commit_types(use_commitlint: bool) -> &'static [&'static str] {
    if use_commitlint {
        COMMITLINT_TYPES
    } else {
        CONVENTIONAL_TYPES
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_validate_conventional_commit() {
        assert!(validate_conventional_commit("feat: add new feature").is_ok());
        assert!(validate_conventional_commit("fix(auth): fix login bug").is_ok());
        assert!(validate_conventional_commit("feat!: breaking change").is_ok());
        assert!(validate_conventional_commit("invalid: message").is_err());
    }

    #[test]
    fn test_validate_semver() {
        assert!(validate_semver("1.0.0").is_ok());
        assert!(validate_semver("v1.2.3").is_ok());
        assert!(validate_semver("2.0.0-beta.1").is_ok());
        assert!(validate_semver("invalid").is_err());
    }

    #[test]
    fn test_validate_email() {
        assert!(validate_email("test@example.com").is_ok());
        assert!(validate_email("invalid-email").is_err());
    }

    #[test]
    fn test_validate_profile_name() {
        assert!(validate_profile_name("personal").is_ok());
        assert!(validate_profile_name("work-company").is_ok());
        assert!(validate_profile_name("").is_err());
        assert!(validate_profile_name("invalid name").is_err());
    }
}
