use crate::core::component::Component;
use crate::core::error::{Error, Result};
use crate::core::plan::PlanStep;
use crate::core::release::types::{ReleaseState, ReleaseStepResult};
use crate::core::release::version;

use super::step_success;

pub(crate) fn run_changelog_finalize(
    step: &PlanStep,
    component: &Component,
    state: &mut ReleaseState,
) -> Result<ReleaseStepResult> {
    let current_version = step
        .inputs
        .get("from")
        .and_then(|value| value.as_str())
        .ok_or_else(|| Error::internal_unexpected("changelog.finalize step missing from"))?;
    let new_version = step
        .inputs
        .get("to")
        .and_then(|value| value.as_str())
        .ok_or_else(|| Error::internal_unexpected("changelog.finalize step missing to"))?;
    let entries = step
        .inputs
        .get("entries")
        .cloned()
        .map(serde_json::from_value)
        .transpose()
        .map_err(|e| Error::internal_json(e.to_string(), Some("changelog entries".to_string())))?;

    version::validate_version_targets_at(component, current_version)?;
    let result = version::validate_and_finalize_changelog(
        component,
        current_version,
        new_version,
        entries.as_ref(),
    )?;
    state.changelog_validation = Some(result.clone());

    let data = serde_json::to_value(&result).map_err(|e| {
        Error::internal_json(e.to_string(), Some("changelog finalize output".to_string()))
    })?;
    Ok(step_success(
        "changelog.finalize",
        "changelog.finalize",
        Some(data),
        Vec::new(),
    ))
}

#[cfg(test)]
mod tests {
    use super::run_changelog_finalize;
    use crate::core::component::{Component, VersionTarget};
    use crate::core::plan::PlanStep;
    use crate::core::release::types::ReleaseState;
    use crate::core::release::ReleaseStepStatus;

    #[test]
    fn test_run_changelog_finalize() {
        let temp = tempfile::tempdir().expect("tempdir");
        let dir = temp.path();
        std::fs::write(
            dir.join("plugin.php"),
            "<?php\n/*\nPlugin Name: Fixture\nVersion: 0.6.12\n*/\n",
        )
        .expect("write plugin");
        std::fs::write(
            dir.join("CHANGELOG.md"),
            "# Changelog\n\nEntries are generated by homeboy from git commits.\n",
        )
        .expect("write changelog");
        let component = Component {
            id: "fixture".to_string(),
            local_path: dir.to_string_lossy().to_string(),
            changelog_target: Some("CHANGELOG.md".to_string()),
            version_targets: Some(vec![VersionTarget {
                file: "plugin.php".to_string(),
                pattern: Some(r"(?:Version|version)[:=]\s+([0-9]+\.[0-9]+\.[0-9]+)".to_string()),
            }]),
            ..Component::default()
        };
        let mut step = PlanStep::ready("changelog.finalize", "changelog.finalize").build();
        step.inputs
            .insert("from".to_string(), serde_json::json!("0.6.12"));
        step.inputs
            .insert("to".to_string(), serde_json::json!("0.6.13"));
        step.inputs.insert(
            "entries".to_string(),
            serde_json::json!({ "Fixed": ["Close release plan gap"] }),
        );
        let mut state = ReleaseState::default();

        let result = run_changelog_finalize(&step, &component, &mut state).expect("finalize");

        assert_eq!(result.status, ReleaseStepStatus::Success);
        assert!(state.changelog_validation.is_some());
        let changelog = std::fs::read_to_string(dir.join("CHANGELOG.md")).expect("read changelog");
        assert!(changelog.contains("## [0.6.13]"));
        assert!(changelog.contains("Close release plan gap"));
    }
}
