mod ai;
mod config;
mod interaction;
mod output;

use anyhow::{anyhow, Context, Result};
use clap::{Args, Parser, Subcommand, ValueEnum};
use config::{AppConfig, SourceConfig};
use dictx_core::{Query, SearchFilters, SearchRequest};
use dictx_index::{build_index, read_metadata, BuildOptions};
use dictx_parser::parser_for;
use dictx_search::{parse_lookup_query, parse_search_query, DictSearcher, SearchResult};
use indicatif::{ProgressBar, ProgressStyle};
use output::{print_entries, print_json_result, print_source_list};
use std::fs;
use std::io::IsTerminal;
use std::path::PathBuf;

const DEFAULT_DISPLAY_LIMIT: usize = 3;
const SEARCH_PREFETCH_LIMIT: usize = 48;

#[derive(Debug, Parser)]
#[command(
    name = "dictx",
    version,
    about = "Rust 终端离线词库 + 可选在线 AI API 词典"
)]
struct Cli {
    #[arg(long, global = true, env = "DICTX_CONFIG")]
    config: Option<PathBuf>,

    #[arg(long, global = true)]
    no_color: bool,

    #[command(subcommand)]
    command: Option<Command>,
}

#[derive(Debug, Subcommand)]
enum Command {
    /// 初始化配置，自动发现当前目录及父目录下的可用词库
    Init(InitArgs),
    /// 精确查词，默认英译中
    #[command(alias = "l")]
    Lookup(LookupArgs),
    /// 中文查英文或中文释义反查
    Zh(ZhArgs),
    /// 全文、模糊、通配符搜索
    Search(SearchArgs),
    /// 构建或重建本地索引
    Build(BuildArgs),
    /// 词典源管理
    Source(SourceCommand),
    /// 配置管理
    Config(ConfigCommand),
    /// 在线 AI API 解释；只有显式调用此命令才联网
    Ai(AiArgs),
}

#[derive(Debug, Args)]
struct InitArgs {
    /// 覆盖已有配置
    #[arg(short, long)]
    force: bool,
}

#[derive(Debug, Args)]
struct LookupArgs {
    word: String,
    #[arg(short, long)]
    source: Option<String>,
    #[arg(short, long)]
    pos: Option<String>,
    #[arg(short, long, value_enum, default_value_t = OutputFormat::Rich)]
    format: OutputFormat,
    #[arg(short = 'n', long, default_value_t = DEFAULT_DISPLAY_LIMIT)]
    limit: usize,
    #[arg(long)]
    raw: bool,
}

#[derive(Debug, Args)]
struct ZhArgs {
    text: String,
    #[arg(short, long)]
    source: Option<String>,
    #[arg(short = 'n', long, default_value_t = DEFAULT_DISPLAY_LIMIT)]
    limit: usize,
    #[arg(short, long, value_enum, default_value_t = OutputFormat::Rich)]
    format: OutputFormat,
}

#[derive(Debug, Args)]
struct SearchArgs {
    query: String,
    #[arg(short, long)]
    source: Option<String>,
    #[arg(short, long)]
    pos: Option<String>,
    #[arg(short, long)]
    tag: Option<String>,
    #[arg(long)]
    collins_min: Option<u8>,
    #[arg(long)]
    freq_min: Option<u32>,
    #[arg(long)]
    freq_max: Option<u32>,
    #[arg(long)]
    oxford_only: bool,
    #[arg(short = 'n', long, default_value_t = DEFAULT_DISPLAY_LIMIT)]
    limit: usize,
    #[arg(long, default_value_t = 0)]
    offset: usize,
    #[arg(short, long, value_enum, default_value_t = OutputFormat::Rich)]
    format: OutputFormat,
}

#[derive(Debug, Args)]
struct BuildArgs {
    #[arg(short, long)]
    source: Option<String>,
    #[arg(short, long)]
    force: bool,
    #[arg(long, default_value_t = 128)]
    ram: usize,
}

#[derive(Debug, Args)]
struct SourceCommand {
    #[command(subcommand)]
    command: SourceSubcommand,
}

#[derive(Debug, Subcommand)]
enum SourceSubcommand {
    /// 列出词典源
    List,
    /// 推荐可下载的高质量开放词库
    Recommend,
    /// 添加词典源
    Add(SourceAddArgs),
    /// 导入词典文件到 DictX 管理目录并添加词典源
    Import(SourceImportArgs),
    /// 移除词典源
    Remove { name: String },
    /// 显示词典源详情
    Info { name: String },
}

#[derive(Debug, Args)]
struct SourceAddArgs {
    name: String,
    path: PathBuf,
    #[arg(short = 'f', long)]
    format: String,
    #[arg(long)]
    display: Option<String>,
    #[arg(long, default_value_t = true)]
    enabled: bool,
}

#[derive(Debug, Args)]
struct SourceImportArgs {
    name: String,
    path: PathBuf,
    #[arg(short = 'f', long)]
    format: String,
    #[arg(long)]
    display: Option<String>,
    #[arg(long, default_value_t = true)]
    enabled: bool,
    #[arg(long)]
    force: bool,
}

#[derive(Debug, Args)]
struct ConfigCommand {
    #[command(subcommand)]
    command: ConfigSubcommand,
}

#[derive(Debug, Subcommand)]
enum ConfigSubcommand {
    Show,
    Path,
    Paths,
    Get { key: String },
    Set { key: String, value: String },
}

#[derive(Debug, Args)]
struct AiArgs {
    text: String,
    #[arg(long)]
    context: Option<String>,
    #[arg(long)]
    model: Option<String>,
}

#[derive(Debug, Clone, Copy, ValueEnum, PartialEq, Eq)]
enum OutputFormat {
    Rich,
    Plain,
    Json,
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    let command = cli.command.unwrap_or(Command::Config(ConfigCommand {
        command: ConfigSubcommand::Show,
    }));

    match command {
        Command::Init(args) => init(cli.config, args.force),
        Command::Lookup(args) => {
            let config = AppConfig::load_or_create(cli.config.as_deref())?;
            let mut request = SearchRequest::new(parse_lookup_query(&args.word));
            request.limit = args.limit;
            request.filters.pos = args.pos;
            run_search(
                &config,
                args.source.as_deref(),
                request,
                args.format,
                args.raw,
                cli.no_color,
            )
        }
        Command::Zh(args) => {
            let config = AppConfig::load_or_create(cli.config.as_deref())?;
            let mut request = SearchRequest::new(Query::Chinese { text: args.text });
            request.limit = args.limit;
            run_search(
                &config,
                args.source.as_deref(),
                request,
                args.format,
                false,
                cli.no_color,
            )
        }
        Command::Search(args) => {
            let config = AppConfig::load_or_create(cli.config.as_deref())?;
            let mut request = SearchRequest::new(parse_search_query(
                &args.query,
                config.search.fuzzy_distance,
            ));
            request.limit = args.limit;
            request.offset = args.offset;
            request.filters = SearchFilters {
                source: None,
                pos: args.pos,
                tag: args.tag,
                collins_min: args.collins_min,
                freq_min: args.freq_min,
                freq_max: args.freq_max,
                oxford_only: args.oxford_only,
            };
            run_search(
                &config,
                args.source.as_deref(),
                request,
                args.format,
                false,
                cli.no_color,
            )
        }
        Command::Build(args) => {
            let config = AppConfig::load_or_create(cli.config.as_deref())?;
            build_sources(&config, args)
        }
        Command::Source(args) => source_command(cli.config, args.command),
        Command::Config(args) => config_command(cli.config, args.command),
        Command::Ai(args) => {
            let config = AppConfig::load_or_create(cli.config.as_deref())?;
            ai::run_ai(&config, args, !cli.no_color && config.output.color)
        }
    }
}

fn init(config_path: Option<PathBuf>, force: bool) -> Result<()> {
    let path = AppConfig::resolve_path(config_path.as_deref())?;
    if path.exists() && !force {
        let mut existing = AppConfig::load_or_create(Some(&path))?;
        if existing.sources.is_empty() {
            let detected = AppConfig::default_with_detected_sources()?.sources;
            if detected.is_empty() {
                return Err(anyhow!(
                    "配置已存在但没有词典源: {}\n当前目录、父目录及 DictX 词库目录没有发现可用词库。\n请使用 dictx source import <NAME> <PATH> --format <FORMAT> 导入，或使用 dictx source add <NAME> <PATH> --format <FORMAT> 引用外部文件。",
                    path.display()
                ));
            }
            existing.sources = detected;
            existing.save_to(&path)?;
            println!("已更新配置: {}", path.display());
            print_init_next_steps(&existing);
            return Ok(());
        }
        let source_summary = existing
            .sources
            .iter()
            .map(|source| format!("  - {} ({})", source.name, source.format))
            .collect::<Vec<_>>()
            .join("\n");
        return Err(anyhow!(
            "配置已存在: {}\n当前已配置 {} 个词典源:\n{}\n\n常用操作:\n  - 查看详情: dictx source list\n  - 构建索引: dictx build\n  - 重新自动发现并覆盖: dictx init --force",
            path.display(),
            existing.sources.len(),
            source_summary
        ));
    }
    let config = AppConfig::default_with_detected_sources()?;
    if config.sources.is_empty() {
        return Err(anyhow!(
            "没有自动发现词典源。\n请把词库放入 DictX 词库目录后运行 dictx init --force，或使用 dictx source import/add 手动添加。"
        ));
    }
    config.save_to(&path)?;
    println!("已写入配置: {}", path.display());
    print_init_next_steps(&config);
    Ok(())
}

fn run_search(
    config: &AppConfig,
    source_filter: Option<&str>,
    mut request: SearchRequest,
    format: OutputFormat,
    raw: bool,
    no_color: bool,
) -> Result<()> {
    let sources = config.enabled_sources(source_filter);
    if sources.is_empty() {
        return Err(anyhow!(
            "没有可用词典源。请先运行 dictx init 或 dictx source add。"
        ));
    }

    request.limit = request.limit.max(1);
    let display_limit = request.limit;
    let mut fetch_request = request.clone();
    if matches!(format, OutputFormat::Rich | OutputFormat::Plain) && !raw {
        fetch_request.limit = display_limit.max(SEARCH_PREFETCH_LIMIT);
    }

    let mut all = Vec::new();
    let mut elapsed = 0u128;
    for source in sources {
        let index_dir = config.index_dir_for(&source.name);
        if !index_dir.exists() {
            continue;
        }
        let searcher = DictSearcher::open(&index_dir)
            .with_context(|| format!("打开索引失败: {}", index_dir.display()))?;
        let result = searcher.search(&fetch_request)?;
        elapsed += result.elapsed_ms;
        all.extend(result.entries);
    }

    all.sort_by(|left, right| {
        right
            .score
            .partial_cmp(&left.score)
            .unwrap_or(std::cmp::Ordering::Equal)
            .then_with(|| left.entry.word_lower.cmp(&right.entry.word_lower))
    });
    let total = all.len();
    let all = all
        .into_iter()
        .take(fetch_request.limit)
        .collect::<Vec<_>>();
    let result = SearchResult {
        entries: all,
        total,
        elapsed_ms: elapsed,
    };

    match format {
        OutputFormat::Json => print_json_result(&request, &result, raw),
        OutputFormat::Plain => {
            print_entries(&request, &result, false, raw, display_limit, false);
            Ok(())
        }
        OutputFormat::Rich => {
            let color = !no_color && config.output.color;
            let interactive = !raw && std::io::stdout().is_terminal();
            let rendered = print_entries(&request, &result, color, raw, display_limit, interactive);
            if raw {
                return Ok(());
            }
            if interactive {
                interaction::run(config, &result, rendered, color)
            } else {
                Ok(())
            }
        }
    }
}

fn build_sources(config: &AppConfig, args: BuildArgs) -> Result<()> {
    fs::create_dir_all(&config.index_dir)?;
    let sources = config.enabled_sources(args.source.as_deref());
    if sources.is_empty() {
        return Err(anyhow!("没有匹配的启用词典源"));
    }

    for source in sources {
        let parser = parser_for(&source.format)?;
        let report = parser.validate(&source.path)?;
        if !report.valid {
            return Err(anyhow!(
                "数据源 {} 校验失败: {}",
                source.name,
                report.issues.join("; ")
            ));
        }

        let index_dir = config.index_dir_for(&source.name);
        let spinner = ProgressBar::new_spinner();
        spinner.set_style(
            ProgressStyle::with_template("{spinner:.cyan} {msg}")
                .unwrap_or_else(|_| ProgressStyle::default_spinner()),
        );
        spinner.enable_steady_tick(std::time::Duration::from_millis(80));
        spinner.set_message(format!(
            "构建 {} ({})...",
            source.display_name(),
            source.path.display()
        ));

        let entries = parser.parse(&source.path)?;
        let stats = build_index(
            &index_dir,
            entries,
            &BuildOptions {
                ram_mb: args.ram,
                force: args.force,
                source_name: source.name.clone(),
                source_path: Some(source.path.clone()),
            },
        )?;
        spinner.finish_and_clear();
        println!(
            "✓ {}: {} 条，{}，{} ms",
            source.display_name(),
            stats.entries,
            human_bytes(stats.index_bytes),
            stats.elapsed_ms
        );
    }
    Ok(())
}

fn source_command(config_path: Option<PathBuf>, command: SourceSubcommand) -> Result<()> {
    let path = AppConfig::resolve_path(config_path.as_deref())?;
    let mut config = AppConfig::load_or_create(Some(&path))?;
    match command {
        SourceSubcommand::List => {
            print_source_list(&config)?;
        }
        SourceSubcommand::Recommend => print_recommended_sources(),
        SourceSubcommand::Add(args) => {
            if config.sources.iter().any(|source| source.name == args.name) {
                return Err(anyhow!("词典源已存在: {}", args.name));
            }
            config.sources.push(SourceConfig {
                name: args.name,
                path: args.path,
                format: args.format,
                display: args.display,
                enabled: args.enabled,
            });
            config.save_to(&path)?;
            println!("已添加词典源");
        }
        SourceSubcommand::Import(args) => {
            let source_path = args.path.canonicalize().with_context(|| {
                format!("无法读取词典文件，请检查路径: {}", args.path.display())
            })?;
            let parser = parser_for(&args.format)?;
            let report = parser.validate(&source_path)?;
            if !report.valid {
                return Err(anyhow!("词典文件校验失败: {}", report.issues.join("; ")));
            }
            let filename = source_path
                .file_name()
                .ok_or_else(|| anyhow!("词典路径缺少文件名: {}", source_path.display()))?;
            let target_dir = config.dict_dir.join(&args.name);
            let target_path = target_dir.join(filename);
            if target_path.exists() && !args.force {
                return Err(anyhow!(
                    "目标词库已存在: {}\n如需覆盖，请追加 --force。",
                    target_path.display()
                ));
            }
            if config.sources.iter().any(|source| source.name == args.name) && !args.force {
                return Err(anyhow!(
                    "词典源已存在: {}\n如需替换，请追加 --force。",
                    args.name
                ));
            }
            fs::create_dir_all(&target_dir)?;
            fs::copy(&source_path, &target_path).with_context(|| {
                format!(
                    "导入词典失败: {} -> {}",
                    source_path.display(),
                    target_path.display()
                )
            })?;

            let imported = SourceConfig {
                name: args.name,
                path: target_path.clone(),
                format: args.format,
                display: args.display,
                enabled: args.enabled,
            };
            config.sources.retain(|source| source.name != imported.name);
            config.sources.push(imported);
            config.save_to(&path)?;
            println!("已导入词典源: {}", target_path.display());
            println!(
                "下一步: dictx build --source {}",
                config.sources.last().unwrap().name
            );
        }
        SourceSubcommand::Remove { name } => {
            let before = config.sources.len();
            config.sources.retain(|source| source.name != name);
            if config.sources.len() == before {
                return Err(anyhow!("没有找到词典源: {name}"));
            }
            config.save_to(&path)?;
            println!("已移除词典源: {name}");
        }
        SourceSubcommand::Info { name } => {
            let source = config
                .sources
                .iter()
                .find(|source| source.name == name)
                .ok_or_else(|| anyhow!("没有找到词典源: {name}"))?;
            println!("{}", toml::to_string_pretty(source)?);
            if let Some(meta) = read_metadata(&config.index_dir_for(&source.name))? {
                println!(
                    "索引: {} 条, {}",
                    meta.entries,
                    human_bytes(meta.index_bytes)
                );
            } else {
                println!("索引: 未构建");
            }
        }
    }
    Ok(())
}

fn print_init_next_steps(config: &AppConfig) {
    println!(
        "配置文件: {}",
        AppConfig::resolve_path(None)
            .map(|p| p.display().to_string())
            .unwrap_or_else(|_| "<未知>".to_string())
    );
    println!("词库目录: {}", config.dict_dir.display());
    println!("索引目录: {}", config.index_dir.display());
    println!();
    println!("已发现 {} 个词典源:", config.sources.len());
    for source in &config.sources {
        println!(
            "  - {} ({})  {}",
            source.name,
            source.format,
            source.path.display()
        );
    }
    println!();
    println!("下一步:");
    println!("  1. 构建索引: dictx build");
    println!("  2. 查询测试: dictx lookup apple / dictx zh 老师");
    println!("  3. 查看词源: dictx source list");
    println!("  4. 导入词库: dictx source import ecdict /path/to/ecdict.csv --format ecdict");
    println!("  5. 获取更多开放词库: dictx source recommend");
}

fn print_recommended_sources() {
    println!("推荐开放词库:");
    println!();
    println!("1. ECDICT 英汉双解");
    println!("   用途: 英->中主词库，含音标、词频、考试标签、柯林斯/牛津标注");
    println!("   许可: MIT");
    println!("   地址: https://github.com/skywind3000/ECDICT");
    println!("   导入: dictx source import ecdict /path/to/ecdict.csv --format ecdict");
    println!();
    println!("2. CC-CEDICT 汉英词典");
    println!("   用途: 中->英补充词库，适合中文词、短语、拼音查询");
    println!("   许可: CC BY-SA");
    println!("   地址: https://cc-cedict.org/wiki/");
    println!("   导入: dictx source import cc_cedict /path/to/cedict_ts.u8 --format cedict");
    println!();
    println!("3. open-dict-data");
    println!("   用途: 多语言开放词典集合，可作为后续适配来源");
    println!("   地址: https://open-dict-data.github.io/");
    println!();
    println!("说明: DictX 不会自动下载大词库文件，避免许可证和体积问题；下载后建议用 source import 复制到 DictX 管理目录，再运行 dictx build。");
}

fn config_command(config_path: Option<PathBuf>, command: ConfigSubcommand) -> Result<()> {
    let path = AppConfig::resolve_path(config_path.as_deref())?;
    let mut config = AppConfig::load_or_create(Some(&path))?;
    match command {
        ConfigSubcommand::Show => {
            println!("{}", toml::to_string_pretty(&config)?);
        }
        ConfigSubcommand::Path => {
            println!("{}", path.display());
        }
        ConfigSubcommand::Paths => {
            println!("config = {}", path.display());
            println!("dict_dir = {}", config.dict_dir.display());
            println!("index_dir = {}", config.index_dir.display());
        }
        ConfigSubcommand::Get { key } => {
            println!(
                "{}",
                config
                    .get_key(&key)
                    .ok_or_else(|| anyhow!("未知配置项: {key}"))?
            );
        }
        ConfigSubcommand::Set { key, value } => {
            config.set_key(&key, &value)?;
            config.save_to(&path)?;
            println!("已更新 {key}");
        }
    }
    Ok(())
}

fn human_bytes(bytes: u64) -> String {
    const UNITS: [&str; 5] = ["B", "KB", "MB", "GB", "TB"];
    let mut value = bytes as f64;
    let mut unit = 0;
    while value >= 1024.0 && unit < UNITS.len() - 1 {
        value /= 1024.0;
        unit += 1;
    }
    if unit == 0 {
        format!("{} {}", bytes, UNITS[unit])
    } else {
        format!("{value:.1} {}", UNITS[unit])
    }
}
