use super::GitRepo;
use anyhow::{bail, Result};
use semver::Version;

/// Tag builder for creating tags
pub struct TagBuilder {
    name: Option<String>,
    message: Option<String>,
    annotate: bool,
    sign: bool,
    force: bool,
    version_prefix: String,
}

impl TagBuilder {
    /// Create new tag builder
    pub fn new() -> Self {
        Self {
            name: None,
            message: None,
            annotate: true,
            sign: false,
            force: false,
            version_prefix: "v".to_string(),
        }
    }

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

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

    /// Create annotated tag
    pub fn annotate(mut self, annotate: bool) -> Self {
        self.annotate = annotate;
        self
    }

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

    /// Force overwrite existing tag
    pub fn force(mut self, force: bool) -> Self {
        self.force = force;
        self
    }

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

    /// Set semantic version
    pub fn version(mut self, version: &Version) -> Self {
        self.name = Some(format!("{}{}", self.version_prefix, version));
        self
    }

    /// Build tag message
    pub fn build_message(&self) -> Result<String> {
        let message = self.message.as_ref()
            .cloned()
            .unwrap_or_else(|| {
                let name = self.name.as_deref().unwrap_or("unknown");
                format!("Release {}", name)
            });
        
        Ok(message)
    }

    /// Execute tag creation
    pub fn execute(&self, repo: &GitRepo) -> Result<()> {
        let name = self.name.as_ref()
            .ok_or_else(|| anyhow::anyhow!("Tag name is required"))?;

        if !self.force {
            let existing_tags = repo.get_tags()?;
            if existing_tags.iter().any(|t| t.name == *name) {
                bail!("Tag '{}' already exists. Use --force to overwrite.", name);
            }
        }

        let message = if self.annotate {
            Some(self.build_message()?.as_str().to_string())
        } else {
            None
        };

        repo.create_tag(name, message.as_deref(), self.sign)?;

        Ok(())
    }

    /// Execute and push tag
    pub fn execute_and_push(&self, repo: &GitRepo, remote: &str) -> Result<()> {
        self.execute(repo)?;
        
        let name = self.name.as_ref().unwrap();
        repo.push(remote, &format!("refs/tags/{}", name))?;
        
        Ok(())
    }
}

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

/// Semantic version bump types
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VersionBump {
    Major,
    Minor,
    Patch,
    Prerelease,
}

impl VersionBump {
    /// Parse from string
    pub fn from_str(s: &str) -> Result<Self> {
        match s.to_lowercase().as_str() {
            "major" => Ok(Self::Major),
            "minor" => Ok(Self::Minor),
            "patch" => Ok(Self::Patch),
            "prerelease" | "pre" => Ok(Self::Prerelease),
            _ => bail!("Invalid version bump: {}. Use: major, minor, patch, prerelease", s),
        }
    }

    /// Get all variants
    pub fn variants() -> &'static [&'static str] {
        &["major", "minor", "patch", "prerelease"]
    }
}

/// Get latest version tag from repository
pub fn get_latest_version(repo: &GitRepo, prefix: &str) -> Result<Option<Version>> {
    let tags = repo.get_tags()?;
    
    let mut versions: Vec<Version> = tags
        .iter()
        .filter_map(|t| {
            let name = &t.name;
            let version_str = name.strip_prefix(prefix).unwrap_or(name);
            Version::parse(version_str).ok()
        })
        .collect();
    
    versions.sort_by(|a, b| b.cmp(a)); // Descending order
    
    Ok(versions.into_iter().next())
}

/// Bump version
pub fn bump_version(version: &Version, bump: VersionBump, prerelease_id: Option<&str>) -> Version {
    match bump {
        VersionBump::Major => Version::new(version.major + 1, 0, 0),
        VersionBump::Minor => Version::new(version.major, version.minor + 1, 0),
        VersionBump::Patch => Version::new(version.major, version.minor, version.patch + 1),
        VersionBump::Prerelease => {
            let pre = prerelease_id.unwrap_or("alpha");
            let pre_id = format!("{}.0", pre);
            Version::parse(&format!("{}-{}", version, pre_id)).unwrap_or_else(|_| version.clone())
        }
    }
}

/// Suggest next version based on commits
pub fn suggest_version_bump(commits: &[super::CommitInfo]) -> VersionBump {
    let mut has_breaking = false;
    let mut has_feature = false;
    let mut has_fix = false;
    
    for commit in commits {
        let msg = commit.message.to_lowercase();
        
        if msg.contains("breaking change") || msg.contains("breaking-change") || msg.contains("breaking_change") {
            has_breaking = true;
        }
        
        if let Some(commit_type) = commit.commit_type() {
            match commit_type.as_str() {
                "feat" => has_feature = true,
                "fix" => has_fix = true,
                _ => {}
            }
        }
    }
    
    if has_breaking {
        VersionBump::Major
    } else if has_feature {
        VersionBump::Minor
    } else if has_fix {
        VersionBump::Patch
    } else {
        VersionBump::Patch
    }
}

/// Generate tag message from commits
pub fn generate_tag_message(version: &str, commits: &[super::CommitInfo]) -> String {
    let mut message = format!("Release {}\n\n", version);
    
    // Group commits by type
    let mut features = vec![];
    let mut fixes = vec![];
    let mut other = vec![];
    let mut breaking = vec![];
    
    for commit in commits {
        let subject = commit.subject();
        
        if commit.message.contains("BREAKING CHANGE") {
            breaking.push(subject.to_string());
        }
        
        if let Some(commit_type) = commit.commit_type() {
            match commit_type.as_str() {
                "feat" => features.push(subject.to_string()),
                "fix" => fixes.push(subject.to_string()),
                _ => other.push(subject.to_string()),
            }
        } else {
            other.push(subject.to_string());
        }
    }
    
    // Build message
    if !breaking.is_empty() {
        message.push_str("## Breaking Changes\n\n");
        for item in &breaking {
            message.push_str(&format!("- {}\n", item));
        }
        message.push('\n');
    }
    
    if !features.is_empty() {
        message.push_str("## Features\n\n");
        for item in &features {
            message.push_str(&format!("- {}\n", item));
        }
        message.push('\n');
    }
    
    if !fixes.is_empty() {
        message.push_str("## Bug Fixes\n\n");
        for item in &fixes {
            message.push_str(&format!("- {}\n", item));
        }
        message.push('\n');
    }
    
    if !other.is_empty() {
        message.push_str("## Other Changes\n\n");
        for item in &other {
            message.push_str(&format!("- {}\n", item));
        }
    }
    
    message
}

/// Tag deletion helper
pub fn delete_tag(repo: &GitRepo, name: &str, remote: Option<&str>) -> Result<()> {
    repo.delete_tag(name)?;
    
    if let Some(remote) = remote {
        use std::process::Command;
        
        let refspec = format!(":refs/tags/{}", name);
        let output = Command::new("git")
            .args(["push", remote, &refspec])
            .current_dir(repo.path())
            .output()?;
        
        if !output.status.success() {
            let stderr = String::from_utf8_lossy(&output.stderr);
            bail!("Failed to delete remote tag: {}", stderr);
        }
    }
    
    Ok(())
}

/// List tags with filtering
pub fn list_tags(
    repo: &GitRepo,
    pattern: Option<&str>,
    limit: Option<usize>,
) -> Result<Vec<super::TagInfo>> {
    let tags = repo.get_tags()?;
    
    let filtered: Vec<_> = tags
        .into_iter()
        .filter(|t| {
            if let Some(p) = pattern {
                t.name.contains(p)
            } else {
                true
            }
        })
        .collect();
    
    if let Some(limit) = limit {
        Ok(filtered.into_iter().take(limit).collect())
    } else {
        Ok(filtered)
    }
}
