use std::path::{Path, PathBuf};

use crate::core::component::{self, Component};
use crate::core::engine::local_files;
use crate::core::engine::validation;
use crate::core::error::Result;
use crate::core::paths::resolve_path;

use super::sections::*;
use super::settings::*;

/// Common changelog file locations to check when the configured path doesn't exist.
/// Ordered by convention preference.
pub const CHANGELOG_CANDIDATES: &[&str] = &[
    "CHANGELOG.md",
    "docs/CHANGELOG.md",
    "changelog.md",
    "docs/changelog.md",
    "doc/CHANGELOG.md",
    "CHANGES.md",
];

pub const INITIAL_CHANGELOG_CONTENT: &str = concat!(
    "# Changelog\n\n",
    "All notable changes to this project will be documented in this file. ",
    "Entries are generated by homeboy from git commits.\n",
);

/// Discover an existing changelog file under `repo_path` by checking the common
/// candidate locations. Returns the relative path (as stored in `changelog_target`)
/// of the first match, or `None` if no candidate exists on disk. (#1128)
pub fn discover_changelog_relative_path(repo_path: &Path) -> Option<String> {
    for candidate in CHANGELOG_CANDIDATES {
        if repo_path.join(candidate).is_file() {
            return Some((*candidate).to_string());
        }
    }
    None
}

pub fn resolve_changelog_path(component: &Component) -> Result<PathBuf> {
    // Validate local_path is absolute and exists before any file operations
    component::validate_local_path(component)?;

    // Require explicit configuration - no auto-detection
    let target = validation::require_with_hints(
        component.changelog_target.as_ref(),
        "component.changelog_target",
        "No changelog configured for this component. Configure the component's changelog_target:",
        vec![
            format!(
                "Use the conventional root changelog:\n  homeboy component set {} --changelog-target \"CHANGELOG.md\"",
                component.id
            ),
            format!(
                "Use a docs changelog:\n  homeboy component set {} --changelog-target \"docs/CHANGELOG.md\"",
                component.id
            ),
        ],
    )?;

    let configured_path = resolve_path(&component.local_path, target);

    // If the configured path exists, use it directly
    if configured_path.exists() {
        return Ok(configured_path);
    }

    // Configured path doesn't exist — try common fallback locations
    let local_path = Path::new(&component.local_path);
    for candidate in CHANGELOG_CANDIDATES {
        let candidate_path = local_path.join(candidate);
        if candidate_path.exists() && candidate_path != configured_path {
            log_status!(
                "changelog",
                "Configured changelog_target '{}' not found, using discovered '{}'. Fix with:\n  homeboy component set {} --changelog-target \"{}\"",
                target,
                candidate,
                component.id,
                candidate
            );
            return Ok(candidate_path);
        }
    }

    // Nothing found — return the configured path (will fail downstream with a
    // clear "file not found" error from the caller that tries to read it)
    Ok(configured_path)
}

#[derive(Debug, Clone)]
pub struct ChangelogSnapshotData {
    pub path: String,
    pub label: String,
    pub items: Vec<String>,
}

#[derive(Debug, Clone)]
pub struct FinalizedReleaseSnapshot {
    pub tag: String,
    pub date: Option<String>,
    pub summary: Option<String>,
}

pub fn read_component_snapshots(
    component: &Component,
) -> Result<(
    Option<FinalizedReleaseSnapshot>,
    Option<ChangelogSnapshotData>,
)> {
    let changelog_path = resolve_changelog_path(component)?;
    let content = local_files::read_file(&changelog_path, "read changelog")?;
    let settings = resolve_effective_settings(Some(component));

    let last_release = extract_last_release_snapshot(&content);
    let unreleased = Some(ChangelogSnapshotData {
        path: changelog_path.to_string_lossy().to_string(),
        label: settings.next_section_label,
        items: get_unreleased_entries(&content, &settings.next_section_aliases),
    });

    Ok((last_release, unreleased))
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::fs;
    use tempfile::TempDir;

    #[test]
    fn discover_changelog_prefers_root_when_both_exist() {
        let dir = TempDir::new().expect("temp dir");
        fs::create_dir_all(dir.path().join("docs")).unwrap();
        fs::write(dir.path().join("CHANGELOG.md"), "# Changelog\n").unwrap();
        fs::write(dir.path().join("docs/CHANGELOG.md"), "# Changelog\n").unwrap();

        assert_eq!(
            discover_changelog_relative_path(dir.path()),
            Some("CHANGELOG.md".to_string())
        );
    }

    #[test]
    fn discover_changelog_finds_docs_path_when_root_absent() {
        // This is the html-to-blocks-converter scenario from #1128: the file
        // lives at docs/CHANGELOG.md but nothing at the repo root.
        let dir = TempDir::new().expect("temp dir");
        fs::create_dir_all(dir.path().join("docs")).unwrap();
        fs::write(dir.path().join("docs/CHANGELOG.md"), "# Changelog\n").unwrap();

        assert_eq!(
            discover_changelog_relative_path(dir.path()),
            Some("docs/CHANGELOG.md".to_string())
        );
    }

    #[test]
    fn discover_changelog_returns_none_when_nothing_exists() {
        let dir = TempDir::new().expect("temp dir");
        assert_eq!(discover_changelog_relative_path(dir.path()), None);
    }
}
