use rusqlite::Transaction;

use super::Database;
use super::error::DatabaseError;
use super::tables::SCHEMA_VERSION;

pub struct MergeReport {
    pub movies: usize,
    pub shows: usize,
    pub seasons: usize,
    pub episodes: usize,
    pub images: usize,
}

impl Database {
    pub fn merge_from(&mut self, remote_path: &str) -> Result<MergeReport, DatabaseError> {
        // Attach remote database
        self.conn.execute(
            &format!("ATTACH DATABASE '{}' AS remote_db", remote_path),
            [],
        )?;

        // Check schema version compatibility
        let remote_version: u32 =
            self.conn
                .pragma_query_value(Some("remote_db"), "user_version", |row| row.get(0))?;
        if remote_version != SCHEMA_VERSION {
            self.conn.execute("DETACH DATABASE remote_db", [])?;
            return Err(DatabaseError::Merge(format!(
                "schema version mismatch: local={}, remote={}",
                SCHEMA_VERSION, remote_version
            )));
        }

        let tx = self.conn.transaction()?;

        // --- Movies: merge TMDB metadata where local is unmatched ---
        let movies = tx.execute(
            "UPDATE movies SET
                tmdb_id = rm.tmdb_id,
                original_title = rm.original_title,
                description = rm.description,
                rating = rm.rating,
                updated_at = CURRENT_TIMESTAMP
            FROM (
                SELECT rm.tmdb_id, rm.original_title, rm.description, rm.rating,
                       rm.file, rl.root_path
                FROM remote_db.movies rm
                JOIN remote_db.libraries rl ON rm.library_id = rl.id
                WHERE rm.tmdb_id IS NOT NULL
            ) AS rm
            JOIN libraries l ON l.root_path = rm.root_path
            WHERE movies.library_id = l.id
              AND movies.file = rm.file
              AND movies.tmdb_id IS NULL",
            [],
        )?;

        let movie_images = copy_images(
            &tx,
            "SELECT m.id, ri.image, ri.filename, ri.mime_type, ri.size, rmi.type
            FROM movies m
            JOIN libraries l ON m.library_id = l.id
            JOIN remote_db.movies rm ON rm.file = m.file
            JOIN remote_db.libraries rl ON rm.library_id = rl.id AND rl.root_path = l.root_path
            JOIN remote_db.movie_images rmi ON rmi.movie_id = rm.id
            JOIN remote_db.images ri ON rmi.image_id = ri.id
            WHERE m.tmdb_id IS NOT NULL
              AND NOT EXISTS (
                SELECT 1 FROM movie_images mi WHERE mi.movie_id = m.id AND mi.type = rmi.type
              )",
            "movie_images",
            "movie_id",
        )?;

        // --- Shows: merge TMDB metadata where local is unmatched ---
        let shows = tx.execute(
            "UPDATE shows SET
                tmdb_id = rs.tmdb_id,
                original_title = rs.original_title,
                release_year = rs.release_year,
                description = rs.description,
                rating = rs.rating,
                updated_at = CURRENT_TIMESTAMP
            FROM remote_db.shows rs
            WHERE shows.title = rs.title
              AND shows.tmdb_id IS NULL
              AND rs.tmdb_id IS NOT NULL",
            [],
        )?;

        let show_images = copy_images(
            &tx,
            "SELECT s.id, ri.image, ri.filename, ri.mime_type, ri.size, rsi.type
            FROM shows s
            JOIN remote_db.shows rs ON rs.title = s.title
            JOIN remote_db.show_images rsi ON rsi.show_id = rs.id
            JOIN remote_db.images ri ON rsi.image_id = ri.id
            WHERE s.tmdb_id IS NOT NULL
              AND NOT EXISTS (
                SELECT 1 FROM show_images si WHERE si.show_id = s.id AND si.type = rsi.type
              )",
            "show_images",
            "show_id",
        )?;

        // --- Seasons: merge TMDB metadata where local is unmatched ---
        let seasons = tx.execute(
            "UPDATE seasons SET
                tmdb_id = rse.tmdb_id,
                title = rse.title,
                release_year = rse.release_year,
                description = rse.description,
                rating = rse.rating,
                updated_at = CURRENT_TIMESTAMP
            FROM remote_db.seasons rse
            JOIN remote_db.shows rs ON rse.show_id = rs.id
            JOIN shows s ON s.title = rs.title
            WHERE seasons.show_id = s.id
              AND seasons.season_number = rse.season_number
              AND seasons.tmdb_id IS NULL
              AND rse.tmdb_id IS NOT NULL",
            [],
        )?;

        let season_images = copy_images(
            &tx,
            "SELECT se.id, ri.image, ri.filename, ri.mime_type, ri.size, rsei.type
            FROM seasons se
            JOIN shows s ON se.show_id = s.id
            JOIN remote_db.shows rs ON rs.title = s.title
            JOIN remote_db.seasons rse ON rse.show_id = rs.id AND rse.season_number = se.season_number
            JOIN remote_db.season_images rsei ON rsei.season_id = rse.id
            JOIN remote_db.images ri ON rsei.image_id = ri.id
            WHERE se.tmdb_id IS NOT NULL
              AND NOT EXISTS (
                SELECT 1 FROM season_images si WHERE si.season_id = se.id AND si.type = rsei.type
              )",
            "season_images",
            "season_id",
        )?;

        // --- Episodes: merge TMDB metadata where local is unmatched ---
        let episodes = tx.execute(
            "UPDATE episodes SET
                tmdb_id = re.tmdb_id,
                title = re.title,
                updated_at = CURRENT_TIMESTAMP
            FROM remote_db.episodes re
            JOIN remote_db.seasons rse ON re.season_id = rse.id
            JOIN remote_db.shows rs ON rse.show_id = rs.id
            JOIN shows s ON s.title = rs.title
            JOIN seasons se ON se.show_id = s.id AND se.season_number = rse.season_number
            WHERE episodes.season_id = se.id
              AND episodes.episode_number = re.episode_number
              AND episodes.tmdb_id IS NULL
              AND re.tmdb_id IS NOT NULL",
            [],
        )?;

        tx.commit()?;
        self.conn.execute("DETACH DATABASE remote_db", [])?;

        Ok(MergeReport {
            movies,
            shows,
            seasons,
            episodes,
            images: movie_images + show_images + season_images,
        })
    }
}

fn copy_images(
    tx: &Transaction,
    select_sql: &str,
    link_table: &str,
    entity_col: &str,
) -> Result<usize, DatabaseError> {
    let mut select_stmt = tx.prepare(select_sql)?;
    let rows: Vec<(i64, Vec<u8>, String, String, i64, String)> = select_stmt
        .query_map([], |row| {
            Ok((
                row.get(0)?,
                row.get(1)?,
                row.get(2)?,
                row.get(3)?,
                row.get(4)?,
                row.get(5)?,
            ))
        })?
        .collect::<Result<_, _>>()?;

    let insert_image_sql =
        "INSERT INTO images (image, filename, mime_type, size) VALUES (?1, ?2, ?3, ?4)";
    let link_sql = format!(
        "INSERT INTO {} ({}, image_id, type) VALUES (?1, ?2, ?3)",
        link_table, entity_col
    );

    let mut count = 0;
    for (entity_id, image, filename, mime_type, size, img_type) in &rows {
        tx.execute(
            insert_image_sql,
            rusqlite::params![image, filename, mime_type, size],
        )?;
        let image_id = tx.last_insert_rowid();
        tx.execute(&link_sql, rusqlite::params![entity_id, image_id, img_type])?;
        count += 1;
    }

    Ok(count)
}
