use crate::error::PipConfError;
use anyhow::{Context, Result};
use indexmap::IndexMap;
use std::fs;
use std::path::{Path, PathBuf};

/// Represents a parsed pip config (INI-style)
#[derive(Debug, Clone, Default)]
pub struct PipConfig {
    /// sections → (key → value)
    pub sections: IndexMap<String, IndexMap<String, String>>,
    pub source_path: Option<PathBuf>,
}

impl PipConfig {
    pub fn new() -> Self {
        Self {
            sections: IndexMap::new(),
            source_path: None,
        }
    }

    /// Parse from file
    pub fn from_file<P: AsRef<Path>>(path: P) -> Result<Self> {
        let path = path.as_ref();
        let content = fs::read_to_string(path)
            .with_context(|| format!("Cannot read config file: {}", path.display()))?;
        let mut cfg = Self::parse(&content, path.to_string_lossy().as_ref())?;
        cfg.source_path = Some(path.to_path_buf());
        Ok(cfg)
    }

    /// Parse raw INI content
    pub fn parse(content: &str, source: &str) -> Result<Self> {
        let mut cfg = PipConfig::new();
        let mut current_section: Option<String> = None;
        let mut current_key: Option<(String, String)> = None; // (section, key) for multiline

        for (line_num, raw_line) in content.lines().enumerate() {
            let lnum = line_num + 1;
            let line = raw_line;

            // Continuation line (multiline value: starts with whitespace)
            if let Some((ref sec, ref key)) = current_key.clone() {
                if line.starts_with(' ') || line.starts_with('\t') {
                    let extra = line.trim();
                    if !extra.is_empty() && !extra.starts_with('#') && !extra.starts_with(';') {
                        let entry = cfg
                            .sections
                            .entry(sec.clone())
                            .or_default()
                            .entry(key.clone())
                            .or_default();
                        entry.push('\n');
                        entry.push_str(extra);
                    }
                    continue;
                } else {
                    current_key = None;
                }
            }

            let stripped = line.trim();

            // Skip blank lines and comments
            if stripped.is_empty() || stripped.starts_with('#') || stripped.starts_with(';') {
                current_key = None;
                continue;
            }

            // Section header
            if stripped.starts_with('[') {
                if let Some(end) = stripped.find(']') {
                    let section_name = stripped[1..end].trim().to_lowercase();
                    cfg.sections.entry(section_name.clone()).or_default();
                    current_section = Some(section_name);
                } else {
                    return Err(PipConfError::ParseError {
                        file: source.to_string(),
                        line: lnum,
                        msg: format!("Invalid section header: {}", stripped),
                    }
                    .into());
                }
                continue;
            }

            // Key = value
            if let Some(sec) = &current_section {
                if let Some(eq_pos) = stripped.find('=') {
                    let key = stripped[..eq_pos].trim().to_string();
                    let value = stripped[eq_pos + 1..].trim().to_string();
                    let value = strip_inline_comment(&value);
                    cfg.sections
                        .entry(sec.clone())
                        .or_default()
                        .insert(key.clone(), value);
                    current_key = Some((sec.clone(), key));
                } else if let Some(colon_pos) = stripped.find(':') {
                    // Support key: value syntax too
                    let key = stripped[..colon_pos].trim().to_string();
                    let value = stripped[colon_pos + 1..].trim().to_string();
                    let value = strip_inline_comment(&value);
                    cfg.sections
                        .entry(sec.clone())
                        .or_default()
                        .insert(key.clone(), value);
                    current_key = Some((sec.clone(), key));
                } else {
                    // Key with no value (boolean flag)
                    let key = stripped.to_string();
                    cfg.sections
                        .entry(sec.clone())
                        .or_default()
                        .insert(key.clone(), "true".to_string());
                    current_key = Some((sec.clone(), key));
                }
            } else {
                return Err(PipConfError::ParseError {
                    file: source.to_string(),
                    line: lnum,
                    msg: format!("Key-value pair outside of any section: {}", stripped),
                }
                .into());
            }
        }

        Ok(cfg)
    }

    /// Get a value
    pub fn get(&self, section: &str, key: &str) -> Option<&String> {
        self.sections.get(section)?.get(key)
    }

    /// Set a value (creates section if needed)
    pub fn set(&mut self, section: &str, key: &str, value: &str) {
        self.sections
            .entry(section.to_string())
            .or_default()
            .insert(key.to_string(), value.to_string());
    }

    /// Remove a key
    pub fn unset(&mut self, section: &str, key: &str) -> bool {
        if let Some(sec) = self.sections.get_mut(section) {
            sec.shift_remove(key).is_some()
        } else {
            false
        }
    }

    /// Append to a multiline list key
    pub fn add_to_list(&mut self, section: &str, key: &str, value: &str) {
        let entry = self
            .sections
            .entry(section.to_string())
            .or_default()
            .entry(key.to_string())
            .or_default();
        if entry.is_empty() {
            *entry = value.to_string();
        } else {
            entry.push('\n');
            entry.push_str(value);
        }
    }

    /// Remove from a multiline list key
    pub fn remove_from_list(&mut self, section: &str, key: &str, value: &str) -> bool {
        if let Some(sec) = self.sections.get_mut(section) {
            if let Some(entry) = sec.get_mut(key) {
                let items: Vec<&str> = entry.lines().collect();
                let new_items: Vec<&str> = items
                    .iter()
                    .copied()
                    .filter(|v| v.trim() != value.trim())
                    .collect();
                if new_items.len() < items.len() {
                    *entry = new_items.join("\n");
                    return true;
                }
            }
        }
        false
    }

    /// Serialize back to INI format
    pub fn to_ini(&self) -> String {
        let mut out = String::new();
        for (section, keys) in &self.sections {
            if keys.is_empty() {
                continue;
            }
            out.push_str(&format!("[{}]\n", section));
            for (key, value) in keys {
                if value.contains('\n') {
                    // Multiline: indent continuation lines
                    let lines: Vec<&str> = value.lines().collect();
                    out.push_str(&format!("{} =\n", key));
                    for line in &lines {
                        out.push_str(&format!("\t{}\n", line));
                    }
                } else {
                    out.push_str(&format!("{} = {}\n", key, value));
                }
            }
            out.push('\n');
        }
        out
    }

    /// Serialize to JSON
    pub fn to_json(&self) -> serde_json::Value {
        let mut map = serde_json::Map::new();
        for (section, keys) in &self.sections {
            let mut sec_map = serde_json::Map::new();
            for (k, v) in keys {
                if v.contains('\n') {
                    let items: Vec<serde_json::Value> = v
                        .lines()
                        .map(|l| serde_json::Value::String(l.trim().to_string()))
                        .collect();
                    sec_map.insert(k.clone(), serde_json::Value::Array(items));
                } else {
                    sec_map.insert(k.clone(), serde_json::Value::String(v.clone()));
                }
            }
            map.insert(section.clone(), serde_json::Value::Object(sec_map));
        }
        serde_json::Value::Object(map)
    }

    /// Serialize to shell env exports
    pub fn to_env_exports(&self) -> String {
        let mut out = String::new();
        out.push_str("# Generated by pipconf\n");
        for (section, keys) in &self.sections {
            for (key, value) in keys {
                let env_key = format!(
                    "PIP_{}_{}",
                    if section == "global" {
                        "".to_string()
                    } else {
                        format!("{}_", section.to_uppercase())
                    },
                    key.to_uppercase().replace('-', "_")
                );
                let env_key = env_key.replace("PIP__", "PIP_");
                let safe_val = value.replace('\n', " ").replace('\'', "\\'");
                out.push_str(&format!("export {}='{}'\n", env_key, safe_val));
            }
        }
        out
    }

    /// Write to file (creates parent directories if needed)
    pub fn write_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
        let path = path.as_ref();
        if let Some(parent) = path.parent() {
            if !parent.as_os_str().is_empty() {
                fs::create_dir_all(parent)
                    .with_context(|| format!("Cannot create directory: {}", parent.display()))?;
            }
        }
        let content = self.to_ini();
        fs::write(path, &content)
            .with_context(|| format!("Cannot write config file: {}", path.display()))
    }

    /// Merge another config into this one
    pub fn merge_from(&mut self, other: &PipConfig, overwrite: bool) {
        for (section, keys) in &other.sections {
            let our_section = self.sections.entry(section.clone()).or_default();
            for (key, value) in keys {
                if overwrite || !our_section.contains_key(key) {
                    our_section.insert(key.clone(), value.clone());
                }
            }
        }
    }

    /// Load and merge all effective configs (site < user < system < env)
    #[allow(dead_code)]
    pub fn load_merged() -> Result<Self> {
        use crate::discover;
        let locs = discover::discover_all();
        let mut merged = PipConfig::new();
        // Reverse: apply site first (lowest prio), then user, system, env last (highest)
        let ordered: Vec<_> = locs.iter().filter(|l| l.exists).collect();
        for loc in ordered {
            match PipConfig::from_file(&loc.path) {
                Ok(cfg) => merged.merge_from(&cfg, true),
                Err(e) => eprintln!("Warning: could not read {}: {}", loc.path.display(), e),
            }
        }
        Ok(merged)
    }
}

fn strip_inline_comment(val: &str) -> String {
    // Remove trailing inline comments (#  or ;)
    // Only if preceded by whitespace (to not break URLs with #)
    let mut in_quote = false;
    let chars: Vec<char> = val.chars().collect();
    let mut i = 0;
    while i < chars.len() {
        let c = chars[i];
        if c == '"' || c == '\'' {
            in_quote = !in_quote;
        }
        if !in_quote && (c == '#' || c == ';') && i > 0 && chars[i - 1] == ' ' {
            return val[..i].trim_end().to_string();
        }
        i += 1;
    }
    val.trim_end().to_string()
}
