use std::io::Write;
use std::time::Instant;

use anyhow::{Context, Error, Result, anyhow};
use clap::error::ErrorKind;
use clap::*;
use log::*;

use directories::ProjectDirs;

use keep::args::{Args, NumberOrString};
use keep::config::Settings;
use keep::db;
use keep::modes;

/**
 * Main function to handle command-line arguments and execute the appropriate mode.
 */
fn main() -> Result<(), Error> {
    use std::fs;
    let proj_dirs = ProjectDirs::from("gt0.ca", "Andrew Phillips", "Keep");

    let mut cmd = Args::command();
    let args = Args::parse();

    // Validate arguments based on mode
    if let Err(e) = args.validate() {
        cmd.error(ErrorKind::ValueValidation, e).exit();
    }

    // Handle --generate-completion early (prints to stdout and exits)
    if let Some(shell) = args.mode.generate_completion {
        clap_complete::generate(shell, &mut Args::command(), "keep", &mut std::io::stdout());
        std::process::exit(0);
    }

    let start = Instant::now();
    let mut builder = env_logger::Builder::new();
    let show_module = args.options.verbose >= 2;
    builder.format(move |buf, record| {
        let elapsed = start.elapsed();
        let ts = format!("[{:>6}.{:03}]", elapsed.as_secs(), elapsed.subsec_millis());
        if show_module {
            writeln!(
                buf,
                "{} {:<5} {}: {}",
                ts,
                record.level(),
                record.module_path().unwrap_or("?"),
                record.args()
            )
        } else {
            writeln!(buf, "{} {:<5} {}", ts, record.level(), record.args())
        }
    });
    let max_level = if args.options.quiet {
        LevelFilter::Off
    } else {
        match args.options.verbose {
            0 => LevelFilter::Warn,
            1 => LevelFilter::Debug,
            _ => LevelFilter::Trace,
        }
    };
    builder.filter_module("keep", max_level);
    builder.init();

    debug!("MAIN: Start");

    // Determine default data directory
    let default_dir = match proj_dirs {
        Some(ref proj_dirs) => proj_dirs.data_dir().to_path_buf(),
        None => return Err(anyhow!("Unable to determine data directory")),
    };

    // Create unified settings using the new config system
    let settings = Settings::new(&args, default_dir)?;

    debug!("MAIN: Loaded settings: {settings:?}");

    let ids = &mut Vec::new();
    let tags = &mut Vec::new();

    // For --info, --get, and --export modes, treat numeric strings as IDs
    for v in args.ids_or_tags.iter() {
        debug!("MAIN: Parsed value: {v:?}");
        match v.clone() {
            NumberOrString::Number(num) => {
                debug!("MAIN: Adding to ids: {num}");
                ids.push(num)
            }
            NumberOrString::Str(str) => {
                // For --info, --get, and --export, try to parse strings as numbers to treat them as IDs
                if (args.mode.info || args.mode.get || args.mode.export)
                    && let Ok(num) = str.parse::<i64>()
                {
                    debug!("MAIN: Adding parsed string to ids: {num}");
                    ids.push(num);
                    continue;
                }
                // If not a number, or not using --info/--get/--export, treat as tag
                debug!("MAIN: Adding to tags: {str}");
                tags.push(str)
            }
        }
    }
    tags.sort();
    tags.dedup();

    /// Internal enum representing the parsed execution mode.
    #[derive(PartialEq, Debug)]
    enum KeepModes {
        Unknown,
        Save,
        Get,
        Diff,
        List,
        Delete,
        Info,
        Update,
        Export,
        Import,
        Status,
        StatusPlugins,
        Server,
        GenerateConfig,
    }

    let mut mode: KeepModes = KeepModes::Unknown;

    if args.mode.save {
        mode = KeepModes::Save;
    } else if args.mode.get {
        mode = KeepModes::Get;
    } else if args.mode.diff {
        mode = KeepModes::Diff;
    } else if args.mode.list {
        mode = KeepModes::List;
    } else if args.mode.delete {
        mode = KeepModes::Delete;
    } else if args.mode.info {
        mode = KeepModes::Info;
    } else if args.mode.update {
        mode = KeepModes::Update;
    } else if args.mode.export {
        mode = KeepModes::Export;
    } else if args.mode.import.is_some() {
        mode = KeepModes::Import;
    } else if args.mode.status {
        mode = KeepModes::Status;
    } else if args.mode.status_plugins {
        mode = KeepModes::StatusPlugins;
    } else if args.mode.server {
        mode = KeepModes::Server;
    } else if args.mode.generate_config {
        mode = KeepModes::GenerateConfig;
    }

    if mode == KeepModes::Unknown {
        if !ids.is_empty() {
            mode = KeepModes::Get;
        } else {
            mode = KeepModes::Save;
        }
    }

    // Validate output format usage
    if let Some(output_format_str) = &settings.output_format
        && output_format_str != "table"
        && mode != KeepModes::Info
        && mode != KeepModes::Status
        && mode != KeepModes::StatusPlugins
        && mode != KeepModes::List
    {
        cmd.error(
                ErrorKind::InvalidValue,
                "--output-format can only be used with --info, --status, --status-plugins, or --list modes"
            ).exit();
    }

    // Validate human-readable usage
    if settings.human_readable && mode != KeepModes::List && mode != KeepModes::Info {
        cmd.error(
            ErrorKind::InvalidValue,
            "--human-readable can only be used with --list and --info modes",
        )
        .exit();
    }

    // Validate server password usage
    if settings.server_password().is_some() && mode != KeepModes::Server {
        cmd.error(
            ErrorKind::InvalidValue,
            "--server-password can only be used with --server mode",
        )
        .exit();
    }

    debug!("MAIN: args: {args:?}");
    debug!("MAIN: ids: {ids:?}");
    debug!("MAIN: tags: {tags:?}");
    debug!("MAIN: mode: {mode:?}");
    debug!("MAIN: settings: {settings:?}");

    // Parse filter chain early for better error reporting
    let filter_chain = if let Some(filter_str) = &args.item.filters {
        match keep::filter_plugin::parse_filter_string(filter_str) {
            Ok(chain) => Some(chain),
            Err(e) => {
                cmd.error(
                    ErrorKind::InvalidValue,
                    format!("Invalid filter string: {e}"),
                )
                .exit();
            }
        }
    } else {
        None
    };

    // Check for client mode
    #[cfg(feature = "client")]
    {
        if let Some(ref client_url) = settings.client_url {
            let client = keep::client::KeepClient::new(
                client_url,
                settings.client_username.clone(),
                settings.client_password.clone(),
                settings.client_jwt.clone(),
            )?;

            return match mode {
                KeepModes::Save => {
                    let metadata: std::collections::HashMap<String, String> = settings
                        .meta
                        .iter()
                        .filter_map(|(k, v)| v.as_ref().map(|val| (k.clone(), val.clone())))
                        .collect();
                    keep::modes::client::save::mode(&client, &mut cmd, &settings, tags, metadata)
                }
                KeepModes::Get => keep::modes::client::get::mode(
                    &client,
                    &mut cmd,
                    &settings,
                    ids,
                    tags,
                    filter_chain,
                ),
                KeepModes::List => {
                    keep::modes::client::list::mode(&client, &mut cmd, &settings, tags)
                }
                KeepModes::Delete => {
                    keep::modes::client::delete::mode(&client, &mut cmd, &settings, ids)
                }
                KeepModes::Info => {
                    keep::modes::client::info::mode(&client, &mut cmd, &settings, ids, tags)
                }
                KeepModes::Diff => {
                    keep::modes::client::diff::mode(&client, &mut cmd, &settings, ids)
                }
                KeepModes::Status => {
                    keep::modes::client::status::mode(&client, &mut cmd, &settings)
                }
                KeepModes::Update => {
                    keep::modes::client::update::mode(&client, &mut cmd, &settings, ids, tags)
                }
                KeepModes::Export => {
                    keep::modes::client::export::mode(&client, &mut cmd, &settings, ids, tags)
                }
                KeepModes::Import => {
                    let meta_file = args.mode.import.as_ref().unwrap();
                    keep::modes::client::import::mode(&client, &mut cmd, &settings, meta_file)
                }
                _ => {
                    cmd.error(
                        ErrorKind::InvalidValue,
                        format!("Mode {mode:?} is not supported in client mode"),
                    )
                    .exit();
                }
            };
        }
    }

    unsafe {
        libc::umask(0o077);
    }

    let data_path = settings.dir.clone();
    let mut db_path = data_path.clone();
    db_path.push("keep-1.db");

    debug!("MAIN: Data directory: {data_path:?}");
    debug!("MAIN: DB file: {db_path:?}");

    // Ensure data directory exists
    fs::create_dir_all(&data_path)
        .with_context(|| format!("Unable to create data directory {data_path:?}"))?;

    // Initialize database
    let mut conn = db::open(db_path.clone())?;

    match mode {
        KeepModes::Save => {
            modes::save::mode_save(&mut cmd, &settings, ids, tags, &mut conn, data_path)
        }
        KeepModes::Get => modes::get::mode_get(
            &mut cmd,
            &settings,
            ids,
            tags,
            &mut conn,
            data_path,
            filter_chain,
        ),
        KeepModes::Diff => modes::diff::mode_diff(&mut cmd, &args, &mut conn),
        KeepModes::List => {
            modes::list::mode_list(&mut cmd, &settings, ids, tags, &mut conn, data_path)
        }
        KeepModes::Delete => modes::delete::mode_delete(
            &mut cmd, &settings, &settings, ids, tags, &mut conn, data_path,
        ),
        KeepModes::Info => {
            modes::info::mode_info(&mut cmd, &settings, ids, tags, &mut conn, data_path)
        }
        KeepModes::Update => {
            modes::update::mode_update(&mut cmd, &settings, ids, tags, &mut conn, data_path)
        }
        KeepModes::Export => modes::export::mode_export(
            &mut cmd,
            &settings,
            ids,
            tags,
            &mut conn,
            data_path,
            filter_chain,
        ),
        KeepModes::Import => {
            let meta_file = args.mode.import.as_ref().unwrap();
            modes::import::mode_import(&mut cmd, &settings, meta_file, &mut conn, data_path)
        }
        KeepModes::Status => modes::status::mode_status(&mut cmd, &settings, data_path, db_path),
        KeepModes::StatusPlugins => {
            modes::status_plugins::mode_status_plugins(&mut cmd, &settings, data_path, db_path)
        }
        KeepModes::Server => {
            #[cfg(feature = "server")]
            {
                modes::server::mode_server(&mut cmd, &settings, &mut conn, data_path)
            }
            #[cfg(not(feature = "server"))]
            {
                cmd.error(
                    ErrorKind::MissingRequiredArgument,
                    "This binary was not compiled with server support. Recompile with --features server"
                ).exit();
            }
        }
        KeepModes::GenerateConfig => {
            modes::generate_config::mode_generate_config(&mut cmd, &settings)
        }
        KeepModes::Unknown => unreachable!(),
    }
}
