use std::sync::Arc;

use brewlog::application::services::{CafeService, GearService, RoastService, RoasterService};
use brewlog::domain::bags::{Bag, BagFilter, BagSortKey, NewBag};
use brewlog::domain::brews::{Brew, BrewFilter, BrewSortKey, NewBrew};
use brewlog::domain::cafes::{Cafe, CafeSortKey, NewCafe};
use brewlog::domain::gear::{Gear, GearCategory, GearFilter, GearSortKey, NewGear};
use brewlog::domain::listing::{ListRequest, PageSize};
use brewlog::domain::repositories::{
    BagRepository, BrewRepository, CafeRepository, GearRepository, RoastRepository,
    RoasterRepository, TimelineEventRepository,
};
use brewlog::domain::roasters::{NewRoaster, Roaster, RoasterSortKey};
use brewlog::domain::roasts::{NewRoast, Roast, RoastSortKey};
use brewlog::domain::timeline::TimelineEvent;
use brewlog::infrastructure::backup::{BackupData, BackupService};
use brewlog::infrastructure::database::{Database, DatabasePool};
use brewlog::infrastructure::repositories::bags::SqlBagRepository;
use brewlog::infrastructure::repositories::brews::SqlBrewRepository;
use brewlog::infrastructure::repositories::cafes::SqlCafeRepository;
use brewlog::infrastructure::repositories::gear::SqlGearRepository;
use brewlog::infrastructure::repositories::roasters::SqlRoasterRepository;
use brewlog::infrastructure::repositories::roasts::SqlRoastRepository;
use brewlog::infrastructure::repositories::timeline_events::SqlTimelineEventRepository;

use super::helpers::{create_default_roaster, spawn_app, spawn_app_with_auth};

struct TestDb {
    pool: DatabasePool,
    roaster_repo: Arc<dyn RoasterRepository>,
    roast_repo: Arc<dyn RoastRepository>,
    bag_repo: Arc<dyn BagRepository>,
    gear_repo: Arc<dyn GearRepository>,
    brew_repo: Arc<dyn BrewRepository>,
    cafe_repo: Arc<dyn CafeRepository>,
    timeline_repo: Arc<dyn TimelineEventRepository>,
    backup_service: BackupService,
    roaster_service: RoasterService,
    roast_service: RoastService,
    gear_service: GearService,
    cafe_service: CafeService,
}

async fn create_test_db() -> TestDb {
    let database = Database::connect("sqlite::memory:")
        .await
        .expect("Failed to connect to in-memory database");

    let pool = database.clone_pool();

    let roaster_repo: Arc<dyn RoasterRepository> =
        Arc::new(SqlRoasterRepository::new(pool.clone()));
    let roast_repo: Arc<dyn RoastRepository> = Arc::new(SqlRoastRepository::new(pool.clone()));
    let bag_repo: Arc<dyn BagRepository> = Arc::new(SqlBagRepository::new(pool.clone()));
    let gear_repo: Arc<dyn GearRepository> = Arc::new(SqlGearRepository::new(pool.clone()));
    let brew_repo: Arc<dyn BrewRepository> = Arc::new(SqlBrewRepository::new(pool.clone()));
    let cafe_repo: Arc<dyn CafeRepository> = Arc::new(SqlCafeRepository::new(pool.clone()));
    let timeline_repo: Arc<dyn TimelineEventRepository> =
        Arc::new(SqlTimelineEventRepository::new(pool.clone()));

    let roaster_service =
        RoasterService::new(Arc::clone(&roaster_repo), Arc::clone(&timeline_repo));
    let roast_service = RoastService::new(
        Arc::clone(&roast_repo),
        Arc::clone(&roaster_repo),
        Arc::clone(&timeline_repo),
    );
    let gear_service = GearService::new(Arc::clone(&gear_repo), Arc::clone(&timeline_repo));
    let cafe_service = CafeService::new(Arc::clone(&cafe_repo), Arc::clone(&timeline_repo));

    TestDb {
        pool: pool.clone(),
        roaster_repo,
        roast_repo,
        bag_repo,
        gear_repo,
        brew_repo,
        cafe_repo,
        timeline_repo,
        backup_service: BackupService::new(pool),
        roaster_service,
        roast_service,
        gear_service,
        cafe_service,
    }
}

fn list_all_request<K: brewlog::domain::listing::SortKey>() -> ListRequest<K> {
    ListRequest::new(
        1,
        PageSize::All,
        K::default(),
        K::default().default_direction(),
    )
}

async fn list_all_roasters(repo: &dyn RoasterRepository) -> Vec<Roaster> {
    repo.list(&list_all_request::<RoasterSortKey>(), None)
        .await
        .expect("failed to list roasters")
        .items
}

async fn list_all_roasts(repo: &dyn RoastRepository) -> Vec<Roast> {
    let page = repo
        .list(&list_all_request::<RoastSortKey>(), None)
        .await
        .expect("failed to list roasts");
    page.items.into_iter().map(|rwr| rwr.roast).collect()
}

async fn list_all_bags(repo: &dyn BagRepository) -> Vec<Bag> {
    let page = repo
        .list(BagFilter::all(), &list_all_request::<BagSortKey>(), None)
        .await
        .expect("failed to list bags");
    page.items.into_iter().map(|bwr| bwr.bag).collect()
}

async fn list_all_gear(repo: &dyn GearRepository) -> Vec<Gear> {
    repo.list(GearFilter::all(), &list_all_request::<GearSortKey>(), None)
        .await
        .expect("failed to list gear")
        .items
}

async fn list_all_cafes(repo: &dyn CafeRepository) -> Vec<Cafe> {
    repo.list(&list_all_request::<CafeSortKey>(), None)
        .await
        .expect("failed to list cafes")
        .items
}

async fn list_all_brews(repo: &dyn BrewRepository) -> Vec<Brew> {
    let page = repo
        .list(BrewFilter::all(), &list_all_request::<BrewSortKey>(), None)
        .await
        .expect("failed to list brews");
    page.items.into_iter().map(|bwd| bwd.brew).collect()
}

async fn list_all_timeline_events(repo: &dyn TimelineEventRepository) -> Vec<TimelineEvent> {
    repo.list_all()
        .await
        .expect("failed to list timeline events")
}

async fn insert_test_image(pool: &DatabasePool, entity_type: &str, entity_id: i64) {
    sqlx::query(
        "INSERT INTO entity_images (entity_type, entity_id, content_type, image_data, thumbnail_data) VALUES (?, ?, ?, ?, ?)",
    )
    .bind(entity_type)
    .bind(entity_id)
    .bind("image/png")
    .bind(b"fake-image-data".as_slice())
    .bind(b"fake-thumb-data".as_slice())
    .execute(pool)
    .await
    .expect("failed to insert test image");
}

async fn count_images(pool: &DatabasePool) -> i64 {
    let row: (i64,) = sqlx::query_as("SELECT COUNT(*) FROM entity_images")
        .fetch_one(pool)
        .await
        .expect("failed to count images");
    row.0
}

/// Populate a database with representative test data and return the key entities.
async fn populate_test_data(db: &TestDb) -> (Roaster, Roast, Bag, Gear, Gear, Gear, Brew, Cafe) {
    // Create roaster (via service to generate timeline event)
    let roaster = db
        .roaster_service
        .create(NewRoaster {
            name: "Square Mile".to_string(),
            country: "UK".to_string(),
            city: Some("London".to_string()),
            homepage: Some("https://shop.squaremilecoffee.com".to_string()),
            created_at: None,
        })
        .await
        .expect("failed to create roaster");

    // Create roast (via service to generate timeline event)
    let roast = db
        .roast_service
        .create(NewRoast {
            roaster_id: roaster.id,
            name: "Red Brick".to_string(),
            origin: "Brazil".to_string(),
            region: "Cerrado".to_string(),
            producer: "Fazenda Passeio".to_string(),
            tasting_notes: vec![
                "Milk Chocolate".to_string(),
                "Hazelnut".to_string(),
                "Caramel".to_string(),
            ],
            process: "Natural".to_string(),
            created_at: None,
        })
        .await
        .expect("failed to create roast");

    // Create bag (250g)
    let bag = db
        .bag_repo
        .insert(NewBag {
            roast_id: roast.id,
            roast_date: Some(chrono::NaiveDate::from_ymd_opt(2025, 1, 15).unwrap()),
            amount: 250.0,
            created_at: None,
        })
        .await
        .expect("failed to create bag");

    // Create gear (via service to generate timeline events)
    let grinder = db
        .gear_service
        .create(NewGear {
            category: GearCategory::Grinder,
            make: "Comandante".to_string(),
            model: "C40 MK4".to_string(),
            created_at: None,
        })
        .await
        .expect("failed to create grinder");

    let brewer = db
        .gear_service
        .create(NewGear {
            category: GearCategory::Brewer,
            make: "Hario".to_string(),
            model: "V60 02".to_string(),
            created_at: None,
        })
        .await
        .expect("failed to create brewer");

    let filter_paper = db
        .gear_service
        .create(NewGear {
            category: GearCategory::FilterPaper,
            make: "Hario".to_string(),
            model: "V60 Tabbed 02".to_string(),
            created_at: None,
        })
        .await
        .expect("failed to create filter paper");

    // Create a brew (deducts 15g from bag, remaining becomes 235)
    let brew = db
        .brew_repo
        .insert(NewBrew {
            bag_id: bag.id,
            coffee_weight: 15.0,
            grinder_id: grinder.id,
            grind_setting: 24.0,
            brewer_id: brewer.id,
            filter_paper_id: Some(filter_paper.id),
            water_volume: 250,
            water_temp: 93.5,
            quick_notes: Vec::new(),
            brew_time: None,
            created_at: None,
        })
        .await
        .expect("failed to create brew");

    // Re-fetch bag to get updated remaining
    let bag = db
        .bag_repo
        .get(bag.id)
        .await
        .expect("failed to re-fetch bag");

    assert_eq!(
        bag.remaining, 235.0,
        "bag remaining should be 235 after brew"
    );

    // Create cafe (via service to generate timeline event)
    let cafe = db
        .cafe_service
        .create(NewCafe {
            name: "Prufrock".to_string(),
            city: "London".to_string(),
            country: "UK".to_string(),
            latitude: 51.5246,
            longitude: -0.1098,
            website: Some("https://prufrockcoffee.com".to_string()),
            created_at: None,
        })
        .await
        .expect("failed to create cafe");

    (
        roaster,
        roast,
        bag,
        grinder,
        brewer,
        filter_paper,
        brew,
        cafe,
    )
}

#[tokio::test]
async fn backup_and_restore_round_trip() {
    // 1. Create source database and populate with test data
    let source = create_test_db().await;
    let (roaster, roast, bag, grinder, brewer, filter_paper, brew, cafe) =
        populate_test_data(&source).await;

    // Insert a test image for the roaster
    insert_test_image(&source.pool, "roaster", i64::from(roaster.id)).await;

    // Verify timeline events were created (roaster + roast inserts create them)
    let source_timeline = list_all_timeline_events(source.timeline_repo.as_ref()).await;
    assert!(
        source_timeline.len() >= 2,
        "expected at least 2 timeline events from roaster+roast creation"
    );

    // 2. Export backup
    let backup_data = source
        .backup_service
        .export()
        .await
        .expect("failed to export backup");

    assert_eq!(backup_data.version, 2);
    assert_eq!(backup_data.roasters.len(), 1);
    assert_eq!(backup_data.roasts.len(), 1);
    assert_eq!(backup_data.bags.len(), 1);
    assert_eq!(backup_data.gear.len(), 3);
    assert_eq!(backup_data.brews.len(), 1);
    assert_eq!(backup_data.cafes.len(), 1);
    assert_eq!(backup_data.timeline_events.len(), source_timeline.len());
    assert_eq!(backup_data.images.len(), 1);
    assert_eq!(backup_data.images[0].entity_type, "roaster");
    assert_eq!(backup_data.images[0].content_type, "image/png");

    // 3. Serialize to JSON and deserialize back (verify serde round-trip)
    let json = serde_json::to_string_pretty(&backup_data).expect("failed to serialize backup");
    let restored_data: BackupData =
        serde_json::from_str(&json).expect("failed to deserialize backup");

    assert_eq!(restored_data.version, 2);
    assert_eq!(restored_data.roasters.len(), 1);
    assert_eq!(restored_data.roasts.len(), 1);
    assert_eq!(restored_data.bags.len(), 1);
    assert_eq!(restored_data.gear.len(), 3);
    assert_eq!(restored_data.brews.len(), 1);
    assert_eq!(restored_data.cafes.len(), 1);
    assert_eq!(restored_data.images.len(), 1);

    // 4. Restore to a fresh database
    let target = create_test_db().await;
    target
        .backup_service
        .restore(restored_data)
        .await
        .expect("failed to restore backup");

    // 5. Verify all data matches

    // Roasters
    let target_roasters = list_all_roasters(target.roaster_repo.as_ref()).await;
    assert_eq!(target_roasters.len(), 1);
    let restored_roaster = &target_roasters[0];
    assert_eq!(restored_roaster.id, roaster.id);
    assert_eq!(restored_roaster.name, roaster.name);
    assert_eq!(restored_roaster.slug, roaster.slug);
    assert_eq!(restored_roaster.country, roaster.country);
    assert_eq!(restored_roaster.city, roaster.city);
    assert_eq!(restored_roaster.homepage, roaster.homepage);
    assert_eq!(restored_roaster.created_at, roaster.created_at);

    // Roasts
    let target_roasts = list_all_roasts(target.roast_repo.as_ref()).await;
    assert_eq!(target_roasts.len(), 1);
    let restored_roast = &target_roasts[0];
    assert_eq!(restored_roast.id, roast.id);
    assert_eq!(restored_roast.roaster_id, roast.roaster_id);
    assert_eq!(restored_roast.name, roast.name);
    assert_eq!(restored_roast.slug, roast.slug);
    assert_eq!(restored_roast.origin, roast.origin);
    assert_eq!(restored_roast.region, roast.region);
    assert_eq!(restored_roast.producer, roast.producer);
    assert_eq!(restored_roast.process, roast.process);
    assert_eq!(restored_roast.tasting_notes, roast.tasting_notes);

    // Bags - critically verify remaining was NOT re-deducted
    let target_bags = list_all_bags(target.bag_repo.as_ref()).await;
    assert_eq!(target_bags.len(), 1);
    let restored_bag = &target_bags[0];
    assert_eq!(restored_bag.id, bag.id);
    assert_eq!(restored_bag.roast_id, bag.roast_id);
    assert_eq!(restored_bag.roast_date, bag.roast_date);
    assert_eq!(restored_bag.amount, 250.0);
    assert_eq!(
        restored_bag.remaining, 235.0,
        "bag remaining should be preserved at 235, not re-deducted by brew restore"
    );
    assert_eq!(restored_bag.closed, bag.closed);

    // Gear
    let target_gear = list_all_gear(target.gear_repo.as_ref()).await;
    assert_eq!(target_gear.len(), 3);
    let grinder_restored = target_gear.iter().find(|g| g.id == grinder.id).unwrap();
    assert_eq!(grinder_restored.category, GearCategory::Grinder);
    assert_eq!(grinder_restored.make, "Comandante");
    assert_eq!(grinder_restored.model, "C40 MK4");
    let brewer_restored = target_gear.iter().find(|g| g.id == brewer.id).unwrap();
    assert_eq!(brewer_restored.category, GearCategory::Brewer);
    let fp_restored = target_gear
        .iter()
        .find(|g| g.id == filter_paper.id)
        .unwrap();
    assert_eq!(fp_restored.category, GearCategory::FilterPaper);

    // Brews
    let target_brews = list_all_brews(target.brew_repo.as_ref()).await;
    assert_eq!(target_brews.len(), 1);
    let restored_brew = &target_brews[0];
    assert_eq!(restored_brew.id, brew.id);
    assert_eq!(restored_brew.bag_id, brew.bag_id);
    assert_eq!(restored_brew.coffee_weight, 15.0);
    assert_eq!(restored_brew.grinder_id, brew.grinder_id);
    assert_eq!(restored_brew.grind_setting, 24.0);
    assert_eq!(restored_brew.brewer_id, brew.brewer_id);
    assert_eq!(restored_brew.filter_paper_id, Some(filter_paper.id));
    assert_eq!(restored_brew.water_volume, 250);
    assert_eq!(restored_brew.water_temp, 93.5);

    // Cafes
    let target_cafes = list_all_cafes(target.cafe_repo.as_ref()).await;
    assert_eq!(target_cafes.len(), 1);
    let restored_cafe = &target_cafes[0];
    assert_eq!(restored_cafe.id, cafe.id);
    assert_eq!(restored_cafe.name, cafe.name);
    assert_eq!(restored_cafe.slug, cafe.slug);
    assert_eq!(restored_cafe.city, cafe.city);
    assert_eq!(restored_cafe.country, cafe.country);
    assert_eq!(restored_cafe.latitude, cafe.latitude);
    assert_eq!(restored_cafe.longitude, cafe.longitude);
    assert_eq!(restored_cafe.website, cafe.website);

    // Timeline events
    let target_timeline = list_all_timeline_events(target.timeline_repo.as_ref()).await;
    assert_eq!(target_timeline.len(), source_timeline.len());
    for (source_event, target_event) in source_timeline.iter().zip(target_timeline.iter()) {
        assert_eq!(target_event.id, source_event.id);
        assert_eq!(target_event.entity_type, source_event.entity_type);
        assert_eq!(target_event.entity_id, source_event.entity_id);
        assert_eq!(target_event.action, source_event.action);
        assert_eq!(target_event.title, source_event.title);
        assert_eq!(target_event.details.len(), source_event.details.len());
        assert_eq!(target_event.tasting_notes, source_event.tasting_notes);
        assert_eq!(target_event.slug, source_event.slug);
        assert_eq!(target_event.roaster_slug, source_event.roaster_slug);
    }

    // Images
    assert_eq!(count_images(&target.pool).await, 1);
    let target_backup = target
        .backup_service
        .export()
        .await
        .expect("failed to re-export");
    assert_eq!(target_backup.images.len(), 1);
    assert_eq!(target_backup.images[0].entity_type, "roaster");
    assert_eq!(target_backup.images[0].image_data, b"fake-image-data");
    assert_eq!(target_backup.images[0].thumbnail_data, b"fake-thumb-data");
}

#[tokio::test]
async fn restore_to_non_empty_database_fails() {
    let db = create_test_db().await;

    // Add a roaster to make the database non-empty
    db.roaster_repo
        .insert(NewRoaster {
            name: "Existing Roaster".to_string(),
            country: "UK".to_string(),
            city: None,
            homepage: None,
            created_at: None,
        })
        .await
        .expect("failed to create roaster");

    // Create a minimal backup
    let backup_data = BackupData {
        version: 1,
        created_at: chrono::Utc::now(),
        roasters: vec![],
        gear: vec![],
        roasts: vec![],
        bags: vec![],
        brews: vec![],
        cafes: vec![],
        cups: vec![],
        timeline_events: vec![],
        images: vec![],
    };

    // Restore should fail because the database is not empty
    let result = db.backup_service.restore(backup_data).await;
    assert!(result.is_err());
    let err_msg = result.unwrap_err().to_string();
    assert!(
        err_msg.contains("not empty"),
        "expected 'not empty' error, got: {err_msg}"
    );
}

#[tokio::test]
async fn backup_empty_database() {
    let db = create_test_db().await;

    let backup_data = db
        .backup_service
        .export()
        .await
        .expect("failed to export empty database");

    assert_eq!(backup_data.version, 2);
    assert!(backup_data.roasters.is_empty());
    assert!(backup_data.roasts.is_empty());
    assert!(backup_data.bags.is_empty());
    assert!(backup_data.gear.is_empty());
    assert!(backup_data.brews.is_empty());
    assert!(backup_data.cafes.is_empty());
    assert!(backup_data.timeline_events.is_empty());
    assert!(backup_data.images.is_empty());

    // Should serialize to valid JSON
    let json = serde_json::to_string_pretty(&backup_data).expect("failed to serialize");
    let parsed: BackupData = serde_json::from_str(&json).expect("failed to deserialize");
    assert_eq!(parsed.version, 2);
}

// --- API-level tests ---

#[tokio::test]
async fn backup_export_requires_auth() {
    let app = spawn_app().await;
    let client = reqwest::Client::new();

    let response = client
        .get(app.api_url("/backup"))
        .send()
        .await
        .expect("failed to send request");

    assert_eq!(response.status(), reqwest::StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn backup_export_returns_data() {
    let app = spawn_app_with_auth().await;
    let client = reqwest::Client::new();

    // Create some data first
    create_default_roaster(&app).await;

    let response = client
        .get(app.api_url("/backup"))
        .bearer_auth(app.auth_token.as_ref().unwrap())
        .send()
        .await
        .expect("failed to send request");

    assert_eq!(response.status(), reqwest::StatusCode::OK);

    let data: BackupData = response.json().await.expect("failed to parse backup data");
    assert_eq!(data.version, 2);
    assert_eq!(data.roasters.len(), 1);
    assert_eq!(data.roasters[0].name, "Test Roasters");
}

#[tokio::test]
async fn backup_restore_requires_auth() {
    let app = spawn_app().await;
    let client = reqwest::Client::new();

    let backup_data = BackupData {
        version: 1,
        created_at: chrono::Utc::now(),
        roasters: vec![],
        gear: vec![],
        roasts: vec![],
        bags: vec![],
        brews: vec![],
        cafes: vec![],
        cups: vec![],
        timeline_events: vec![],
        images: vec![],
    };

    let response = client
        .post(app.api_url("/backup/restore"))
        .json(&backup_data)
        .send()
        .await
        .expect("failed to send request");

    assert_eq!(response.status(), reqwest::StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn backup_restore_non_empty_db_returns_conflict() {
    let app = spawn_app_with_auth().await;
    let client = reqwest::Client::new();

    // Create data to make the database non-empty
    create_default_roaster(&app).await;

    let backup_data = BackupData {
        version: 1,
        created_at: chrono::Utc::now(),
        roasters: vec![],
        gear: vec![],
        roasts: vec![],
        bags: vec![],
        brews: vec![],
        cafes: vec![],
        cups: vec![],
        timeline_events: vec![],
        images: vec![],
    };

    let response = client
        .post(app.api_url("/backup/restore"))
        .bearer_auth(app.auth_token.as_ref().unwrap())
        .json(&backup_data)
        .send()
        .await
        .expect("failed to send request");

    assert_eq!(response.status(), reqwest::StatusCode::CONFLICT);
}

#[tokio::test]
async fn backup_round_trip_via_api() {
    // 1. Create source app with data
    let source = spawn_app_with_auth().await;
    let client = reqwest::Client::new();

    create_default_roaster(&source).await;

    // 2. Export via API
    let response = client
        .get(source.api_url("/backup"))
        .bearer_auth(source.auth_token.as_ref().unwrap())
        .send()
        .await
        .expect("failed to export backup");

    assert_eq!(response.status(), reqwest::StatusCode::OK);
    let backup_data: BackupData = response.json().await.expect("failed to parse backup");
    assert_eq!(backup_data.roasters.len(), 1);

    // 3. Restore into a fresh app
    let target = spawn_app_with_auth().await;

    let response = client
        .post(target.api_url("/backup/restore"))
        .bearer_auth(target.auth_token.as_ref().unwrap())
        .json(&backup_data)
        .send()
        .await
        .expect("failed to restore backup");

    assert_eq!(response.status(), reqwest::StatusCode::NO_CONTENT);

    // 4. Verify data was restored by listing roasters
    let response = client
        .get(target.api_url("/roasters"))
        .send()
        .await
        .expect("failed to list roasters");

    let roasters: Vec<Roaster> = response.json().await.expect("failed to parse roasters");
    assert_eq!(roasters.len(), 1);
    assert_eq!(roasters[0].name, "Test Roasters");
}

// --- Reset tests ---

#[tokio::test]
async fn reset_requires_auth() {
    let app = spawn_app().await;
    let client = reqwest::Client::new();

    let response = client
        .post(app.api_url("/backup/reset"))
        .send()
        .await
        .expect("failed to send request");

    assert_eq!(response.status(), reqwest::StatusCode::UNAUTHORIZED);
}

#[tokio::test]
async fn reset_clears_all_data() {
    let db = create_test_db().await;
    let (roaster, ..) = populate_test_data(&db).await;

    // Insert an image
    insert_test_image(&db.pool, "roaster", i64::from(roaster.id)).await;

    // Verify data exists before reset
    assert!(!list_all_roasters(db.roaster_repo.as_ref()).await.is_empty());
    assert!(!list_all_roasts(db.roast_repo.as_ref()).await.is_empty());
    assert!(!list_all_bags(db.bag_repo.as_ref()).await.is_empty());
    assert!(!list_all_gear(db.gear_repo.as_ref()).await.is_empty());
    assert!(!list_all_brews(db.brew_repo.as_ref()).await.is_empty());
    assert!(!list_all_cafes(db.cafe_repo.as_ref()).await.is_empty());
    assert!(
        !list_all_timeline_events(db.timeline_repo.as_ref())
            .await
            .is_empty()
    );
    assert_eq!(count_images(&db.pool).await, 1);

    // Reset
    db.backup_service
        .reset()
        .await
        .expect("failed to reset database");

    // Verify all tables are empty
    assert!(list_all_roasters(db.roaster_repo.as_ref()).await.is_empty());
    assert!(list_all_roasts(db.roast_repo.as_ref()).await.is_empty());
    assert!(list_all_bags(db.bag_repo.as_ref()).await.is_empty());
    assert!(list_all_gear(db.gear_repo.as_ref()).await.is_empty());
    assert!(list_all_brews(db.brew_repo.as_ref()).await.is_empty());
    assert!(list_all_cafes(db.cafe_repo.as_ref()).await.is_empty());
    assert!(
        list_all_timeline_events(db.timeline_repo.as_ref())
            .await
            .is_empty()
    );
    assert_eq!(count_images(&db.pool).await, 0);
}

#[tokio::test]
async fn reset_then_restore_succeeds() {
    let db = create_test_db().await;
    populate_test_data(&db).await;

    // Export before reset
    let backup = db.backup_service.export().await.expect("failed to export");
    assert_eq!(backup.roasters.len(), 1);
    assert_eq!(backup.gear.len(), 3);
    assert_eq!(backup.brews.len(), 1);

    // Reset
    db.backup_service
        .reset()
        .await
        .expect("failed to reset database");

    // Restore should succeed (database is now empty)
    db.backup_service
        .restore(backup)
        .await
        .expect("failed to restore after reset");

    // Verify data is back
    assert_eq!(list_all_roasters(db.roaster_repo.as_ref()).await.len(), 1);
    assert_eq!(list_all_roasts(db.roast_repo.as_ref()).await.len(), 1);
    assert_eq!(list_all_bags(db.bag_repo.as_ref()).await.len(), 1);
    assert_eq!(list_all_gear(db.gear_repo.as_ref()).await.len(), 3);
    assert_eq!(list_all_brews(db.brew_repo.as_ref()).await.len(), 1);
    assert_eq!(list_all_cafes(db.cafe_repo.as_ref()).await.len(), 1);
}
