#[tokio::test]
async fn all_groups_pinned_deletes_nothing() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			target_free: 1_000_000,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![crate::MockTorrent {
				files: vec![
					crate::TorrentFile { size: 60 },
					crate::TorrentFile { size: 40 },
				],
				torrent: crate::Torrent {
					category: "blocked".into(),
					hash: "1111111111111111111111111111111111111111".parse().unwrap(),
					selected_size: 100,
					total_size: 100,
					..crate::Torrent::zero()
				},
			}],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert!(g.qbt.deleted().is_empty());
}

#[tokio::test]
async fn copies_weight_groups_two_torrents() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			copies_weight: 1.0,
			target_free: 1,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 120 },
						crate::TorrentFile { size: 80 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						selected_size: 200,
						total_size: 200,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	let mut deleted = g.qbt.deleted();
	deleted.sort_unstable();
	assert_eq!(deleted, vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn delete_limit_data_stops_early() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			delete_limit_data: Some(150),
			size_weight: 1.0,
			target_free: 1_000_000,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 55 },
						crate::TorrentFile { size: 35 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 90,
						total_size: 90,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 50 },
						crate::TorrentFile { size: 30 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						selected_size: 80,
						total_size: 80,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn delete_limit_groups_stops_early() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			delete_limit_groups: Some(std::num::NonZeroUsize::new(2).unwrap()),
			size_weight: 1.0,
			target_free: 1_000_000,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 55 },
						crate::TorrentFile { size: 35 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 90,
						total_size: 90,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 50 },
						crate::TorrentFile { size: 30 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						selected_size: 80,
						total_size: 80,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 45 },
						crate::TorrentFile { size: 25 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "4444444444444444444444444444444444444444".parse().unwrap(),
						selected_size: 70,
						total_size: 70,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn deletes_everything_when_target_unreachable() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			size_weight: 1.0,
			target_free: 1_000_000,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 30 },
						crate::TorrentFile { size: 20 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 50,
						total_size: 50,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn deletes_highest_ranked_until_target_met() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			size_weight: 1.0,
			target_free: 120,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 30 },
						crate::TorrentFile { size: 20 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 50,
						total_size: 50,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 20 },
						crate::TorrentFile { size: 10 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						selected_size: 30,
						total_size: 30,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn dry_run_deletes_nothing() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			size_weight: 1.0,
			target_free: 1_000_000,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 30 },
						crate::TorrentFile { size: 20 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 50,
						total_size: 50,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, true).await.unwrap();

	assert!(g.qbt.deleted().is_empty());
}

#[tokio::test]
async fn nothing_to_do_when_free_space_above_target() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			target_free: 100,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			free_space: 200,
			torrents: vec![crate::MockTorrent {
				files: vec![
					crate::TorrentFile { size: 30 },
					crate::TorrentFile { size: 20 },
				],
				torrent: crate::Torrent {
					category: "ok".into(),
					hash: "1111111111111111111111111111111111111111".parse().unwrap(),
					selected_size: 50,
					total_size: 50,
					..crate::Torrent::zero()
				},
			}],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert!(g.qbt.deleted().is_empty());
}

#[tokio::test]
async fn seeder_count_weight_orders_deletion() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			seeder_count_weight: 1.0,
			target_free: 1,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 100 },
						crate::TorrentFile { size: 10 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						seeders: 5,
						selected_size: 110,
						total_size: 110,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 120 },
						crate::TorrentFile { size: 80 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						seeders: 1,
						selected_size: 200,
						total_size: 200,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 100 },
						crate::TorrentFile { size: 20 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						seeders: 9,
						selected_size: 120,
						total_size: 120,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"3333333333333333333333333333333333333333".parse().unwrap(),
	]);
}

#[tokio::test]
async fn small_files_excluded_from_group_id() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["ok".into()].into(),
			copies_weight: 1.0,
			target_free: 1,
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 100 },
						crate::TorrentFile { size: 100 },
						crate::TorrentFile { size: 5 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 205,
						total_size: 205,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 100 },
						crate::TorrentFile { size: 100 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 200,
						total_size: 200,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 300 },
						crate::TorrentFile { size: 300 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "3333333333333333333333333333333333333333".parse().unwrap(),
						selected_size: 600,
						total_size: 600,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	let mut deleted = g.qbt.deleted();
	deleted.sort_unstable();
	assert_eq!(deleted, vec![
		"1111111111111111111111111111111111111111".parse().unwrap(),
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}

#[tokio::test]
async fn pinned_by_explicit_rule_never_deleted() {
	let g = crate::Global {
		config: crate::Config {
			categories_allowed: ["keep".into(), "ok".into()].into(),
			size_weight: 1.0,
			target_free: 1_000_000,
			rules: vec![crate::Rule {
				match_: crate::Match {
					categories: Some(["keep".into()].into()),
					..Default::default()
				},
				pin: Some(true),
				..Default::default()
			}],
			..crate::Config::empty()
		},
		now: std::time::SystemTime::UNIX_EPOCH,
		qbt: crate::QbtMock {
			torrents: vec![
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 600 },
						crate::TorrentFile { size: 400 },
					],
					torrent: crate::Torrent {
						category: "keep".into(),
						hash: "1111111111111111111111111111111111111111".parse().unwrap(),
						selected_size: 1000,
						total_size: 1000,
						..crate::Torrent::zero()
					},
				},
				crate::MockTorrent {
					files: vec![
						crate::TorrentFile { size: 60 },
						crate::TorrentFile { size: 40 },
					],
					torrent: crate::Torrent {
						category: "ok".into(),
						hash: "2222222222222222222222222222222222222222".parse().unwrap(),
						selected_size: 100,
						total_size: 100,
						..crate::Torrent::zero()
					},
				},
			],
			..Default::default()
		},
	};

	crate::cmd_clean(&g, false).await.unwrap();

	assert_eq!(g.qbt.deleted(), vec![
		"2222222222222222222222222222222222222222".parse().unwrap(),
	]);
}
