use anyhow::Result;
use sqlx::SqlitePool;

use super::client::LeagueApiClient;

#[derive(Debug)]
pub struct SyncResult {
    pub teams: i64,
    pub players: i64,
    pub transactions: i64,
}

/// Sync all teams from the league API for the given season.
/// Uses INSERT OR REPLACE to handle both inserts and updates atomically.
pub async fn sync_teams(pool: &SqlitePool, season: i64, client: &LeagueApiClient) -> Result<i64> {
    let response = client.get_teams(Some(season), None, false, false).await?;

    let mut tx = pool.begin().await?;
    let mut count: i64 = 0;
    let synced_at = chrono::Utc::now().naive_utc();

    for data in response.teams {
        let manager1_name = data.manager1.and_then(|m| m.name);
        let manager2_name = data.manager2.and_then(|m| m.name);
        let gm_discord_id = data.gm_discord_id;
        let gm2_discord_id = data.gm2_discord_id;
        let division_id = data.division.as_ref().map(|d| d.id).flatten();
        let division_name = data.division.as_ref().and_then(|d| d.division_name.clone());
        let league_abbrev = data.division.as_ref().and_then(|d| d.league_abbrev.clone());

        sqlx::query(
            "INSERT OR REPLACE INTO teams \
             (id, abbrev, short_name, long_name, season, manager1_name, manager2_name, \
              gm_discord_id, gm2_discord_id, division_id, division_name, league_abbrev, \
              thumbnail, color, dice_color, stadium, salary_cap, synced_at) \
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
        )
        .bind(data.id)
        .bind(data.abbrev.unwrap_or_default())
        .bind(data.short_name.unwrap_or_default())
        .bind(data.long_name.unwrap_or_default())
        .bind(season)
        .bind(manager1_name)
        .bind(manager2_name)
        .bind(gm_discord_id)
        .bind(gm2_discord_id)
        .bind(division_id)
        .bind(division_name)
        .bind(league_abbrev)
        .bind(data.thumbnail)
        .bind(data.color)
        .bind(data.dice_color)
        .bind(data.stadium)
        .bind(data.salary_cap)
        .bind(synced_at)
        .execute(&mut *tx)
        .await?;

        count += 1;
    }

    tx.commit().await?;
    crate::db::queries::update_sync_status(pool, "teams", count, None).await?;
    Ok(count)
}

/// Sync players from the league API.
/// Uses ON CONFLICT DO UPDATE with an explicit column list that intentionally omits `hand`,
/// since `hand` is populated only from CSV card imports and must not be overwritten by the API.
pub async fn sync_players(
    pool: &SqlitePool,
    season: i64,
    team_id: Option<i64>,
    client: &LeagueApiClient,
) -> Result<i64> {
    let team_ids = team_id.map(|id| vec![id]);
    let response = client
        .get_players(Some(season), team_ids.as_deref(), None, None, false)
        .await?;

    let mut tx = pool.begin().await?;
    let mut count: i64 = 0;
    let synced_at = chrono::Utc::now().naive_utc();

    for data in response.players {
        let player_team_id = data.team.map(|t| t.id);

        sqlx::query(
            "INSERT INTO players \
             (id, name, season, team_id, swar, card_image, card_image_alt, headshot, vanity_card, \
              pos_1, pos_2, pos_3, pos_4, pos_5, pos_6, pos_7, pos_8, \
              injury_rating, il_return, demotion_week, strat_code, bbref_id, sbaplayer_id, \
              last_game, last_game2, synced_at) \
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) \
             ON CONFLICT(id) DO UPDATE SET \
               name=excluded.name, \
               season=excluded.season, \
               team_id=excluded.team_id, \
               swar=excluded.swar, \
               card_image=excluded.card_image, \
               card_image_alt=excluded.card_image_alt, \
               headshot=excluded.headshot, \
               vanity_card=excluded.vanity_card, \
               pos_1=excluded.pos_1, \
               pos_2=excluded.pos_2, \
               pos_3=excluded.pos_3, \
               pos_4=excluded.pos_4, \
               pos_5=excluded.pos_5, \
               pos_6=excluded.pos_6, \
               pos_7=excluded.pos_7, \
               pos_8=excluded.pos_8, \
               injury_rating=excluded.injury_rating, \
               il_return=excluded.il_return, \
               demotion_week=excluded.demotion_week, \
               strat_code=excluded.strat_code, \
               bbref_id=excluded.bbref_id, \
               sbaplayer_id=excluded.sbaplayer_id, \
               last_game=excluded.last_game, \
               last_game2=excluded.last_game2, \
               synced_at=excluded.synced_at",
        )
        .bind(data.id)
        .bind(data.name.unwrap_or_default())
        .bind(season)
        .bind(player_team_id)
        .bind(data.swar)
        .bind(data.card_image)
        .bind(data.card_image_alt)
        .bind(data.headshot)
        .bind(data.vanity_card)
        .bind(data.pos_1)
        .bind(data.pos_2)
        .bind(data.pos_3)
        .bind(data.pos_4)
        .bind(data.pos_5)
        .bind(data.pos_6)
        .bind(data.pos_7)
        .bind(data.pos_8)
        .bind(data.injury_rating)
        .bind(data.il_return)
        .bind(data.demotion_week)
        .bind(data.strat_code)
        .bind(data.bbref_id)
        .bind(data.sbaplayer_id)
        .bind(data.last_game)
        .bind(data.last_game2)
        .bind(synced_at)
        .execute(&mut *tx)
        .await?;

        count += 1;
    }

    tx.commit().await?;
    crate::db::queries::update_sync_status(pool, "players", count, None).await?;
    Ok(count)
}

/// Sync transactions from the league API for the given season/week range.
/// Skips rows where move_id or player is missing.
/// ON CONFLICT only updates mutable fields (cancelled, frozen, synced_at) to preserve original
/// season/week/team data.
pub async fn sync_transactions(
    pool: &SqlitePool,
    season: i64,
    week_start: i64,
    week_end: Option<i64>,
    team_abbrev: Option<&str>,
    client: &LeagueApiClient,
) -> Result<i64> {
    let abbrev_vec: Vec<&str> = team_abbrev.into_iter().collect();
    let abbrev_slice: Option<&[&str]> =
        if abbrev_vec.is_empty() { None } else { Some(&abbrev_vec) };

    let response = client
        .get_transactions(season, week_start, week_end, abbrev_slice, false, false, false)
        .await?;

    let mut tx = pool.begin().await?;
    let mut count: i64 = 0;
    let synced_at = chrono::Utc::now().naive_utc();

    for data in response.transactions {
        let (Some(move_id), Some(player)) = (data.move_id, data.player) else {
            continue;
        };

        let player_id = player.id;
        // Schema has from_team_id/to_team_id as NOT NULL; use 0 as sentinel for missing teams
        let from_team_id = data.oldteam.map(|t| t.id).unwrap_or(0);
        let to_team_id = data.newteam.map(|t| t.id).unwrap_or(0);
        let week = data.week.unwrap_or(0);
        let cancelled = data.cancelled.unwrap_or(false);
        let frozen = data.frozen.unwrap_or(false);

        sqlx::query(
            "INSERT INTO transactions \
             (season, week, move_id, player_id, from_team_id, to_team_id, cancelled, frozen, \
              synced_at) \
             VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) \
             ON CONFLICT(move_id, player_id) DO UPDATE SET \
               cancelled=excluded.cancelled, \
               frozen=excluded.frozen, \
               synced_at=excluded.synced_at",
        )
        .bind(season)
        .bind(week)
        .bind(&move_id)
        .bind(player_id)
        .bind(from_team_id)
        .bind(to_team_id)
        .bind(cancelled)
        .bind(frozen)
        .bind(synced_at)
        .execute(&mut *tx)
        .await?;

        count += 1;
    }

    tx.commit().await?;
    crate::db::queries::update_sync_status(pool, "transactions", count, None).await?;
    Ok(count)
}

/// Orchestrate a full sync: teams, players, and transactions for the configured season.
pub async fn sync_all(pool: &SqlitePool, settings: &crate::config::Settings) -> Result<SyncResult> {
    let client = LeagueApiClient::new(
        &settings.api.base_url,
        &settings.api.api_key,
        settings.api.timeout,
    )?;

    let season = settings.team.season as i64;

    let teams = sync_teams(pool, season, &client).await?;
    let players = sync_players(pool, season, None, &client).await?;
    // Start from week 1 (Python defaults to 0 which includes pre-season moves)
    let transactions = sync_transactions(pool, season, 1, None, None, &client).await?;

    Ok(SyncResult { teams, players, transactions })
}
