#![allow(dead_code)]
use chrono::{Datelike, NaiveDate};
use regex::Regex;
use serde::Serialize;
use serde_json::json;
use std::collections::HashMap;

fn transliterate(text: &str, _reversed: bool) -> String {
    let mapping = [
        ("а", "a"),
        ("б", "b"),
        ("в", "v"),
        ("г", "g"),
        ("д", "d"),
        ("е", "e"),
        ("ё", "yo"),
        ("ж", "zh"),
        ("з", "z"),
        ("и", "i"),
        ("й", "y"),
        ("к", "k"),
        ("л", "l"),
        ("м", "m"),
        ("н", "n"),
        ("о", "o"),
        ("п", "p"),
        ("р", "r"),
        ("с", "s"),
        ("т", "t"),
        ("у", "u"),
        ("ф", "f"),
        ("х", "h"),
        ("ц", "ts"),
        ("ч", "ch"),
        ("ш", "sh"),
        ("щ", "sch"),
        ("ъ", ""),
        ("ы", "y"),
        ("ь", ""),
        ("э", "e"),
        ("ю", "yu"),
        ("я", "ya"),
        ("А", "A"),
        ("Б", "B"),
        ("В", "V"),
        ("Г", "G"),
        ("Д", "D"),
        ("Е", "E"),
        ("Ё", "Yo"),
        ("Ж", "Zh"),
        ("З", "Z"),
        ("И", "I"),
        ("Й", "Y"),
        ("К", "K"),
        ("Л", "L"),
        ("М", "M"),
        ("Н", "N"),
        ("О", "O"),
        ("П", "P"),
        ("Р", "R"),
        ("С", "S"),
        ("Т", "T"),
        ("У", "U"),
        ("Ф", "F"),
        ("Х", "H"),
        ("Ц", "Ts"),
        ("Ч", "Ch"),
        ("Ш", "Sh"),
        ("Щ", "Sch"),
        ("Ъ", ""),
        ("Ы", "Y"),
        ("Ь", ""),
        ("Э", "E"),
        ("Ю", "Yu"),
        ("Я", "Ya"),
    ];
    let mut result = text.to_string();
    for (cyr, lat) in mapping {
        result = result.replace(cyr, lat);
    }
    result
}

#[derive(Debug, Clone, Serialize)]
pub struct Template {
    pub src: String,
    pub composition: String,
    #[serde(rename = "outputModule")]
    pub output_module: String,
    #[serde(rename = "outputExt")]
    pub output_ext: String,
}

impl Template {
    pub fn double(
        src: String,
        composition: String,
        output_module: String,
        output_ext: String,
    ) -> Self {
        Self {
            src,
            composition,
            output_module,
            output_ext,
        }
    }

    pub fn single(
        src: String,
        composition: String,
        output_module: String,
        output_ext: String,
    ) -> Self {
        Self {
            src,
            composition,
            output_module,
            output_ext,
        }
    }
}

#[derive(Debug, Clone, Serialize)]
#[serde(tag = "module")]
pub enum PostrenderAction {
    #[serde(rename = "@nexrender/action-encode")]
    Encode { preset: String, output: String },
    #[serde(rename = "@nexrender/action-copy")]
    Copy { input: String, output: String },
}

#[derive(Debug, Clone, Serialize)]
#[serde(untagged)]
pub enum Asset {
    Data {
        #[serde(rename = "type")]
        asset_type: String,
        #[serde(rename = "layerName")]
        layer_name: String,
        property: String,
        value: serde_json::Value,
    },
    DataExpression {
        #[serde(rename = "type")]
        asset_type: String,
        #[serde(rename = "layerName")]
        layer_name: String,
        property: String,
        expression: String,
    },
    Image {
        src: String,
        #[serde(rename = "type")]
        asset_type: String,
        #[serde(rename = "layerName")]
        layer_name: String,
        #[serde(skip_serializing_if = "std::ops::Not::not")]
        cache: bool,
    },
    Video {
        src: String,
        #[serde(rename = "type")]
        asset_type: String,
        #[serde(rename = "layerName")]
        layer_name: String,
        #[serde(skip_serializing_if = "std::ops::Not::not")]
        cache: bool,
    },
}

#[derive(Debug, Clone, Serialize)]
pub struct NexrenderJob {
    pub template: Template,
    pub actions: Actions,
    pub assets: Vec<Asset>,
}

#[derive(Debug, Clone, Serialize)]
pub struct Actions {
    pub postrender: Vec<PostrenderAction>,
}

impl Default for Actions {
    fn default() -> Self {
        Self {
            postrender: vec![PostrenderAction::Encode {
                preset: "mp4".to_string(),
                output: "encoded.mp4".to_string(),
            }],
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct JobData {
    pub date: String,
    pub time: String,
    pub time_h: String,
    pub time_m: String,
    pub channel: String,
    pub sport: String,
    pub league: String,
    pub team_a: String,
    pub team_b: String,
    pub data_display: String,
    pub pack_path: String,
    pub team_a_logo: String,
    pub team_b_logo: String,
    pub team_a_logo_res: Option<String>,
    pub team_b_logo_res: Option<String>,
    pub channel_logo: String,
    pub outfile_name: String,
    pub row_index: usize,
    // Поля для сортировки
    pub sort_date: NaiveDate,
    pub sort_time: String,
    pub variant_order: u8,
}

impl JobData {
    pub fn from_row(
        row: &HashMap<String, String>,
        row_index: usize,
        packs: &HashMap<String, String>,
        logos: &LogoRegistry,
        channels: &HashMap<String, String>,
    ) -> Option<Self> {
        let state = row.get("STATE").map(|s| s.as_str()).unwrap_or("");
        if state != "FALSE" {
            return None;
        }

        let sport = row.get("SPORT")?.clone();
        let league = row.get("LEAGUE")?.clone();
        let team_a = row.get("TEAM A").cloned().unwrap_or_default();
        let team_b = row.get("TEAM B").cloned().unwrap_or_default();
        let channel = row.get("CHANEL").cloned().unwrap_or_default();
        let time = row.get("TIME")?.clone();
        let data_str = row.get("DATA")?.clone();

        let (date_obj, data_display) = parse_date(&data_str);
        let (time_h, time_m) = parse_time(&time);
        let sort_time = format!("{}:{}", time_h, time_m);

        let pack_path = packs.get(&sport).cloned().unwrap_or_default();
        let channel_logo = channels.get(&channel).cloned().unwrap_or_default();

        let (team_a_name, team_a_res) = parse_team_name(&team_a);
        let (team_b_name, team_b_res) = parse_team_name(&team_b);

        let team_a_logo = logos.find(&team_a, &sport).unwrap_or_default();
        let team_b_logo = logos.find(&team_b, &sport).unwrap_or_default();

        let mut fn_parts = Vec::new();
        fn_parts.push(format!(
            "{:04}{:02}{:02}",
            date_obj.year(),
            date_obj.month(),
            date_obj.day()
        ));
        if !sport.is_empty() && sport != "Без оформления" {
            fn_parts.push(sport.clone());
        }
        fn_parts.push(league.trim_end_matches('.').to_string());
        if !team_a_name.is_empty() {
            fn_parts.push(team_a_name.clone());
        }
        if !team_b_name.is_empty() {
            fn_parts.push(team_b_name.clone());
        }
        if !channel.is_empty() {
            fn_parts.push(channel.clone());
        }

        let mut outfile_name = transliterate(&fn_parts.join("_"), true)
            .replace(' ', "-")
            .replace('\'', "");

        // Заменяем 2 и более дефисов подряд на один
        let re = Regex::new(r"-{2,}").unwrap();
        outfile_name = re.replace_all(&outfile_name, "-").to_string();

        // Убираем дефисы в начале и конце
        outfile_name = outfile_name.trim_matches('-').to_string();

        Some(Self {
            date: data_str,
            time: time.clone(),
            time_h,
            time_m,
            channel,
            sport,
            league,
            team_a: team_a_name,
            team_b: team_b_name,
            data_display,
            pack_path: unc_to_uri(&pack_path),
            team_a_logo: unc_to_uri(&team_a_logo),
            team_b_logo: unc_to_uri(&team_b_logo),
            team_a_logo_res: team_a_res,
            team_b_logo_res: team_b_res,
            channel_logo: unc_to_uri(&channel_logo),
            outfile_name,
            row_index,
            sort_date: date_obj,
            sort_time,
            variant_order: 0,
        })
    }

    pub fn create_variants(&self) -> Vec<Self> {
        let mut variants = Vec::new();

        // Оригинал (0)
        let mut original = self.clone();
        original.variant_order = 0;
        variants.push(original);

        // Today (1)
        let mut today = self.clone();
        today.data_display = "сегодня".to_string();
        today.outfile_name = format!("{}_Today", self.outfile_name);
        today.variant_order = 1;
        variants.push(today);

        // Tomorrow (2)
        let mut tomorrow = self.clone();
        tomorrow.data_display = "завтра".to_string();
        tomorrow.outfile_name = format!("{}_Tomorrow", self.outfile_name);
        tomorrow.variant_order = 2;
        variants.push(tomorrow);

        variants
    }

    pub fn to_nexrender_job(&self, config: &crate::config::Config) -> NexrenderJob {
        let template = if self.team_b.is_empty() {
            Template::single(
                config.template_single_src.clone(),
                config.template_composition.clone(),
                config.template_output_module.clone(),
                config.template_output_ext.clone(),
            )
        } else {
            Template::double(
                config.template_double_src.clone(),
                config.template_composition.clone(),
                config.template_output_module.clone(),
                config.template_output_ext.clone(),
            )
        };

        let output_path = format!(
            "{}/{}.{}",
            config.output_folder, self.outfile_name, config.template_output_ext
        );

        let mut actions = Actions::default();
        actions.postrender.push(PostrenderAction::Copy {
            input: format!("encoded.{}", config.template_output_ext),
            output: output_path,
        });

        let mut assets = Vec::new();
        self.add_text_assets(&mut assets);
        self.add_image_assets(&mut assets);
        if !self.pack_path.is_empty() {
            assets.push(Asset::Video {
                src: self.pack_path.clone(),
                asset_type: "video".to_string(),
                layer_name: "TOP".to_string(),
                cache: true,
            });
        }

        NexrenderJob {
            template,
            actions,
            assets,
        }
    }

    fn add_text_assets(&self, assets: &mut Vec<Asset>) {
        // DATA — всегда
        assets.push(Asset::Data {
            asset_type: "data".to_string(),
            layer_name: "DATA".to_string(),
            property: "Source Text".to_string(),
            value: json!(self.data_display),
        });
        self.add_date_adjustments(assets);

        // TIME — зависит от шаблона
        if !self.team_b.is_empty() {
            // DOUBLE: TIME_H и TIME_M
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "TIME_H".to_string(),
                property: "Source Text".to_string(),
                value: json!(self.time_h),
            });
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "TIME_M".to_string(),
                property: "Source Text".to_string(),
                value: json!(self.time_m),
            });
            self.add_time_adjustments(assets);
        } else {
            // SINGLE: TIME
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "TIME".to_string(),
                property: "Source Text".to_string(),
                value: json!(self.time),
            });
        }

        // LEAGUE — всегда
        assets.push(Asset::Data {
            asset_type: "data".to_string(),
            layer_name: "LEAGUE".to_string(),
            property: "Source Text".to_string(),
            value: json!(self.league),
        });
        self.add_league_adjustments(assets);

        // SPORT — всегда
        if !self.sport.is_empty() {
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "SPORT".to_string(),
                property: "Source Text".to_string(),
                value: json!(self.sport),
            });
        }

        // TEAMS — ТОЛЬКО для DOUBLE шаблона
        if !self.team_b.is_empty() {
            let teams_text = format!("{} - {}", self.team_a, self.team_b);
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "TEAMS".to_string(),
                property: "Source Text".to_string(),
                value: json!(teams_text),
            });
            self.add_teams_adjustments(assets);
        }
    }

    fn add_date_adjustments(&self, assets: &mut Vec<Asset>) {
        let (font_size, anchor_point): (&str, Vec<i32>) = match self.data_display.as_str() {
            "сегодня" if !self.team_b.is_empty() => ("105", vec![0, 5]),
            "завтра" if !self.team_b.is_empty() => ("115", vec![0, 25]),
            _ if self.data_display.len() < 6 && !self.team_b.is_empty() => ("120", vec![0, 20]),
            _ => return,
        };
        assets.push(Asset::Data {
            asset_type: "data".to_string(),
            layer_name: "DATA".to_string(),
            property: "Source Text.fontSize".to_string(),
            value: json!(font_size),
        });
        assets.push(Asset::Data {
            asset_type: "data".to_string(),
            layer_name: "DATA".to_string(),
            property: "transform.anchorPoint".to_string(),
            value: json!(anchor_point),
        });
    }

    fn add_time_adjustments(&self, assets: &mut Vec<Asset>) {
        let anchor_point = if self.time_h.len() < 2 {
            vec![60, 0]
        } else if self.time_h.len() == 2 && self.time_h.parse::<i32>().unwrap_or(20) < 20 {
            vec![20, 0]
        } else {
            return;
        };
        for layer in ["TIME_H", "TIME_M", "TIME"] {
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: layer.to_string(),
                property: "transform.anchorPoint".to_string(),
                value: json!(anchor_point),
            });
        }
    }

    fn add_league_adjustments(&self, assets: &mut Vec<Asset>) {
        if self.league.len() > 16 {
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "LEAGUE".to_string(),
                property: "Source Text.fontSize".to_string(),
                value: json!("73"),
            });
        }
    }

    fn add_teams_adjustments(&self, assets: &mut Vec<Asset>) {
        if (self.team_a.len() + self.team_b.len()) >= 32 {
            assets.push(Asset::Data {
                asset_type: "data".to_string(),
                layer_name: "TEAMS".to_string(),
                property: "Source Text.fontSize".to_string(),
                value: json!("55"),
            });
        }
    }

    fn add_image_assets(&self, assets: &mut Vec<Asset>) {
        // Логотипы команд — НЕ кешируем (часто меняются)
        if !self.team_a_logo.is_empty() {
            assets.push(Asset::Image {
                src: self.team_a_logo.clone(),
                asset_type: "image".to_string(),
                layer_name: "TEAM_A_LOGO".to_string(),
                cache: false,
            });
            if let Some(res) = &self.team_a_logo_res {
                assets.push(Asset::DataExpression {
                    asset_type: "data".to_string(),
                    layer_name: "TEAM_A_LOGO".to_string(),
                    property: "scale".to_string(),
                    expression: logo_scale_expression(res),
                });
            }
        }
        if !self.team_b_logo.is_empty() {
            assets.push(Asset::Image {
                src: self.team_b_logo.clone(),
                asset_type: "image".to_string(),
                layer_name: "TEAM_B_LOGO".to_string(),
                cache: false,
            });
            if let Some(res) = &self.team_b_logo_res {
                assets.push(Asset::DataExpression {
                    asset_type: "data".to_string(),
                    layer_name: "TEAM_B_LOGO".to_string(),
                    property: "scale".to_string(),
                    expression: logo_scale_expression(res),
                });
            }
        }
        // Логотип канала — КЕШИРУЕМ (редко меняется)
        if !self.channel_logo.is_empty() {
            assets.push(Asset::Image {
                src: self.channel_logo.clone(),
                asset_type: "image".to_string(),
                layer_name: "CHANELL".to_string(),
                cache: true,
            });
        }
    }
}

#[derive(Debug, Clone, Default)]
pub struct LogoRegistry {
    logos: HashMap<(String, String), String>,
}

impl LogoRegistry {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn with_capacity(capacity: usize) -> Self {
        Self {
            logos: HashMap::with_capacity(capacity),
        }
    }

    pub fn insert(&mut self, team: String, sport: String, link: String) {
        self.logos.insert((team, sport), link);
    }

    pub fn find(&self, team: &str, sport: &str) -> Option<String> {
        log::debug!("LogoRegistry::find: team='{}', sport='{}'", team, sport);

        if let Some(link) = self.logos.get(&(team.to_string(), sport.to_string())) {
            log::debug!("Found exact match: {}", link);
            return Some(link.clone());
        }

        for ((t, s), link) in &self.logos {
            if s == sport && team.starts_with(t) {
                log::debug!("Found prefix match: t='{}', link='{}'", t, link);
                return Some(link.clone());
            }
        }

        log::warn!("No logo found for team='{}', sport='{}'", team, sport);
        None
    }

    /// Оставляет только указанные команды, удаляя все остальные
    pub fn retain<F>(&mut self, mut predicate: F)
    where
        F: FnMut(&str, &str) -> bool,
    {
        self.logos.retain(|(team, sport), _| predicate(team, sport));
    }

    /// Возвращает количество записей в реестре
    pub fn len(&self) -> usize {
        self.logos.len()
    }
}

fn parse_date(s: &str) -> (NaiveDate, String) {
    let parts: Vec<&str> = s.split('.').collect();
    if parts.len() == 3 {
        if let (Ok(day), Ok(month), Ok(year)) = (
            parts[0].parse::<u32>(),
            parts[1].parse::<u32>(),
            parts[2].parse::<i32>(),
        ) {
            if let Some(date) = NaiveDate::from_ymd_opt(year, month, day) {
                let months = [
                    "",
                    "января",
                    "февраля",
                    "марта",
                    "апреля",
                    "мая",
                    "июня",
                    "июля",
                    "августа",
                    "сентября",
                    "октября",
                    "ноября",
                    "декабря",
                ];
                return (date, format!("{} {}", day, months[month as usize]));
            }
        }
    }
    (NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(), s.to_string())
}

fn parse_time(s: &str) -> (String, String) {
    let parts: Vec<&str> = s.split(':').collect();
    if parts.len() >= 2 {
        (parts[0].to_string(), parts[1].to_string())
    } else {
        (s.to_string(), "00".to_string())
    }
}

fn parse_team_name(s: &str) -> (String, Option<String>) {
    let parts: Vec<&str> = s.split('#').collect();
    if parts.len() >= 3 {
        (parts[0].to_string(), Some(parts[2].to_string()))
    } else {
        (parts[0].to_string(), None)
    }
}

fn unc_to_uri(path: &str) -> String {
    if path.is_empty() {
        return String::new();
    }

    // Уже правильный URI
    if path.starts_with("http://") || path.starts_with("https://") || path.starts_with("file://") {
        return path.to_string();
    }

    // Mac SMB путь: smb://server/share/path -> file://server/share/path
    if path.starts_with("smb://") {
        let without_smb = path.strip_prefix("smb:").unwrap_or(path);
        return format!("file:{}", without_smb.replace('\\', "/"));
    }

    // Windows UNC путь: \\server\share\path -> file://server/share/path
    let normalized = path.replace('\\', "/");

    // Убираем ведущие слеши (для UNC их быть не должно)
    let trimmed = normalized.trim_start_matches('/');

    format!("file://{}", trimmed)
}

fn logo_scale_expression(target_size: &str) -> String {
    format!(
        "if (width > height) {{max_size = width;}} else {{max_size = height;}} var real_size = {}/max_size*100;[real_size,real_size]",
        target_size
    )
}
