use super::*;
use crate::BumpSeverity;
use crate::PackageRecord;
use crate::PublishState;

#[test]
fn package_snapshot_file_lookup_finds_matching_paths() {
	let snapshot = PackageSnapshot {
		label: "after".to_string(),
		files: vec![PackageSnapshotFile {
			path: PathBuf::from("src/lib.rs"),
			contents: "pub fn greet() {}".to_string(),
		}],
	};

	let file = snapshot
		.file(Path::new("src/lib.rs"))
		.unwrap_or_else(|| panic!("expected file in snapshot"));
	assert_eq!(file.contents, "pub fn greet() {}");
}

#[test]
fn package_analysis_context_exposes_package_root() {
	let package = PackageRecord::new(
		Ecosystem::Cargo,
		"core",
		PathBuf::from("/repo/crates/core/Cargo.toml"),
		PathBuf::from("/repo"),
		None,
		PublishState::Public,
	);
	let context = PackageAnalysisContext {
		repo_root: Path::new("/repo"),
		package: &package,
		detection_level: DetectionLevel::Signature,
		changed_files: &[],
		before_snapshot: None,
		after_snapshot: None,
	};

	assert_eq!(context.package_root(), Path::new("/repo/crates/core"));
}

#[test]
fn api_snapshot_sorts_items_for_stable_json_output() {
	let snapshot = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![
			ApiItem::new("function", "crate::z", Some("pub fn z()".to_string())),
			ApiItem::new("function", "crate::a", Some("pub fn a()".to_string())),
		],
		Vec::new(),
	);

	let json = serde_json::to_string_pretty(&snapshot)
		.unwrap_or_else(|error| panic!("serialize snapshot: {error}"));

	assert!(json.contains("\"schemaVersion\": 1"));
	assert!(
		json.find("function:crate::a") < json.find("function:crate::z"),
		"items should be sorted by stable id: {json}"
	);
}

#[test]
fn api_snapshot_diff_classifies_removed_added_and_modified_items() {
	let before = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![
			ApiItem::new(
				"function",
				"crate::removed",
				Some("pub fn removed()".to_string()),
			),
			ApiItem::new(
				"function",
				"crate::changed",
				Some("pub fn changed()".to_string()),
			),
		],
		Vec::new(),
	);
	let after = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![
			ApiItem::new(
				"function",
				"crate::changed",
				Some("pub fn changed(value: u8)".to_string()),
			),
			ApiItem::new(
				"function",
				"crate::added",
				Some("pub fn added()".to_string()),
			),
		],
		Vec::new(),
	);

	let diff = before.diff(&after);

	assert_eq!(diff.suggested_bump, BumpSeverity::Major);
	assert_eq!(diff.changes.len(), 3);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.kind == ApiChangeKind::Removed)
	);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.kind == ApiChangeKind::Added)
	);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.kind == ApiChangeKind::Modified)
	);
}

#[test]
fn diff_api_snapshots_reports_added_removed_modified_and_warnings() {
	let before = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![
			ApiItem::new(
				"function",
				"crate::removed",
				Some("pub fn removed()".to_string()),
			),
			ApiItem::new(
				"function",
				"crate::changed",
				Some("pub fn changed()".to_string()),
			),
			ApiItem::new("function", "crate::same", Some("pub fn same()".to_string())),
		],
		vec!["before warning".to_string()],
	);
	let after = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![
			ApiItem::new(
				"function",
				"crate::added",
				Some("pub fn added()".to_string()),
			),
			ApiItem::new(
				"function",
				"crate::changed",
				Some("pub fn changed(value: u8)".to_string()),
			),
			ApiItem::new("function", "crate::same", Some("pub fn same()".to_string())),
		],
		vec!["after warning".to_string()],
	);

	let diff = diff_api_snapshots(&before, &after);

	assert_eq!(diff.suggested_bump, BumpSeverity::Major);
	assert_eq!(diff.changes.len(), 3);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.summary.contains("added"))
	);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.summary.contains("removed"))
	);
	assert!(
		diff.changes
			.iter()
			.any(|change| change.summary.contains("changed"))
	);
	assert_eq!(diff.warnings, vec!["before warning", "after warning"]);
}

#[test]
fn api_diff_is_empty_when_no_changes_are_present() {
	let before = ApiSnapshot::new(
		"core",
		"core",
		Ecosystem::Cargo,
		"cargo/public-api",
		vec![ApiItem::new(
			"function",
			"crate::same",
			Some("pub fn same()".to_string()),
		)],
		Vec::new(),
	);
	let after = before.clone();

	let diff = diff_api_snapshots(&before, &after);

	assert!(diff.is_empty());
	assert_eq!(diff.suggested_bump, BumpSeverity::None);
}
