use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, RwLock};
use std::time::Duration;

use athena_rs::AthenaClient;
use clap::{Parser, ValueEnum};
use dexter_sentinel::audit::AuditBus;
use dexter_sentinel::scrape::tax::{run_tax_harvest, TaxHarvestConfig, TaxSourceMode};
use dexter_sentinel::scrape::DexterAthena;

#[derive(Debug, Clone, Copy, ValueEnum)]
enum SourceModeCli {
    CuratedJson,
    Web,
}

impl From<SourceModeCli> for TaxSourceMode {
    fn from(value: SourceModeCli) -> Self {
        match value {
            SourceModeCli::CuratedJson => TaxSourceMode::CuratedJson,
            SourceModeCli::Web => TaxSourceMode::Web,
        }
    }
}

const CLI_AFTER_HELP: &str = concat!(
    "Examples:\n",
    "  cargo run --bin tax-harvest -- --country-code NL --tax-year 2026 --source-mode curated-json --input ./tax-data/nl-2026.json --no-insert\n",
    "  cargo run --bin tax-harvest -- --country-code PL --tax-year 2026 --source-mode web --source-url https://example.test/poland-tax-2026.json --no-insert\n\n",
    "Cargo tip:\n",
    "  Put '--' after cargo flags so tax-harvest receives its own args.\n",
    "  Show all options with: cargo run --bin tax-harvest -- --help"
);

const SOURCE_MODE_REQUIREMENTS: &str = concat!(
    "Source mode requirements:\n",
    "  - curated-json => --input <PATH>\n",
    "  - web          => --source-url <URL>\n",
    "Run `cargo run --bin tax-harvest -- --help` for examples."
);

#[derive(Parser, Debug)]
#[command(
    name = "tax-harvest",
    about = "Harvest country/year tax obligations and insert normalized rows into tax_types/tax_rates/tax_due_dates",
    after_help = CLI_AFTER_HELP
)]
struct Cli {
    /// Two-letter ISO country code (e.g., NL, PL, CZ).
    #[arg(short = 'c', long)]
    country_code: String,

    /// Tax year for the harvest payload (e.g., 2026).
    #[arg(short = 'y', long)]
    tax_year: i32,

    /// Source mode for harvesting tax payloads.
    #[arg(
        long,
        value_enum,
        value_name = "MODE",
        default_value_t = SourceModeCli::CuratedJson,
        help = "Tax source mode (curated-json | web)",
        long_help = "Tax source mode.\n- curated-json: requires --input <PATH>\n- web: requires --source-url <URL>"
    )]
    source_mode: SourceModeCli,

    /// Input JSON file path for curated-json mode.
    #[arg(
        long,
        value_name = "PATH",
        help_heading = "Source options"
    )]
    input: Option<PathBuf>,

    /// Source endpoint for web mode (JSON scaffold).
    #[arg(
        long,
        value_name = "URL",
        help_heading = "Source options"
    )]
    source_url: Option<String>,

    /// URL for the Dexter Athena Gateway.
    #[arg(long, default_value = "https://mirror3.athena-db.com")]
    athena_url: String,

    /// API key for the Dexter Athena Gateway (or ATHENA_API_KEY env).
    #[arg(long, env = "ATHENA_API_KEY", hide_env_values = true)]
    athena_key: Option<String>,

    /// Value of the X-Athena-Client header.
    #[arg(long, default_value = "dexter")]
    athena_client: String,

    /// Optional X-User-Id header for Athena inserts.
    #[arg(long, env = "ATHENA_USER_ID")]
    athena_user_id: Option<String>,

    /// Optional X-Company-Id header for Athena inserts.
    #[arg(long, env = "ATHENA_COMPANY_ID")]
    athena_company_id: Option<String>,

    /// Optional X-Organization-Id header for Athena inserts.
    #[arg(long, env = "ATHENA_ORGANIZATION_ID")]
    athena_organization_id: Option<String>,

    /// Max concurrent network inserts targeting Athena.
    #[arg(long, default_value_t = 25)]
    max_workers: usize,

    /// Dry run: normalize and validate only, no Athena inserts.
    #[arg(long, default_value_t = false)]
    no_insert: bool,

    /// Load HTTP/proxy settings from Athena table `http_request_profiles` (requires insert mode).
    #[arg(long)]
    http_profile: Option<String>,

    /// Override `proxy_url` for this run.
    #[arg(long)]
    proxy_url: Option<String>,
}

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    dotenv::dotenv().ok();
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();
    let cc = cli.country_code.trim().to_uppercase();
    if cc.len() != 2 || !cc.chars().all(|c| c.is_ascii_alphabetic()) {
        anyhow::bail!("--country-code must be ISO 3166-1 alpha-2 (e.g. NL)");
    }
    if !(1900..=2500).contains(&cli.tax_year) {
        anyhow::bail!("--tax-year must be between 1900 and 2500");
    }

    let source_url = cli
        .source_url
        .as_deref()
        .map(str::trim)
        .filter(|s| !s.is_empty())
        .map(str::to_string);
    match cli.source_mode {
        SourceModeCli::CuratedJson if cli.input.is_none() => {
            anyhow::bail!(
                "--input is required when --source-mode curated-json\n\n{}",
                SOURCE_MODE_REQUIREMENTS
            );
        }
        SourceModeCli::Web if source_url.is_none() => {
            anyhow::bail!(
                "--source-url is required and cannot be empty when --source-mode web\n\n{}",
                SOURCE_MODE_REQUIREMENTS
            );
        }
        _ => {}
    }

    let insert = !cli.no_insert;
    let (dexter, http) = if insert {
        let athena_key: String = cli
            .athena_key
            .filter(|s| !s.is_empty())
            .or_else(|| std::env::var("ATHENA_API_KEY").ok())
            .filter(|s| !s.is_empty())
            .ok_or_else(|| {
                anyhow::anyhow!("set --athena-key or ATHENA_API_KEY when not using --no-insert")
            })?;
        let base_trim: String = cli.athena_url.trim_end_matches('/').to_string();
        let sdk: Arc<AthenaClient> =
            Arc::new(AthenaClient::new(&base_trim, &athena_key, &cli.athena_client).await?);
        let audit: AuditBus = dexter_sentinel::audit::spawn_audit_flusher(
            Arc::clone(&sdk),
            None,
            8192,
            100,
            Duration::from_secs(2),
        );
        let prof: Option<dexter_sentinel::http_profile::HttpClientProfile> =
            if let Some(ref n) = cli.http_profile {
                let cache = Arc::new(RwLock::new(HashMap::new()));
                dexter_sentinel::data::http_profiles::resolve_profile_cached(Some(&sdk), &cache, n)
                    .await
            } else {
                None
            };
        if cli.http_profile.is_some() && prof.is_none() {
            anyhow::bail!("--http-profile not found, disabled, or not loaded");
        }
        let audit_label: Option<String> = if cli.proxy_url.is_some() {
            Some("cli_override".to_string())
        } else {
            cli.http_profile.clone()
        };
        let http = dexter_sentinel::request::client::build_client_for_cli(
            prof.as_ref(),
            cli.proxy_url.as_deref(),
            true,
        )?;
        let http_loop = http.clone();
        let dex = DexterAthena::new_with_arc(
            sdk,
            base_trim,
            http,
            athena_key,
            cli.athena_client.clone(),
            cli.athena_user_id.clone(),
            cli.athena_company_id.clone(),
            cli.athena_organization_id.clone(),
            None,
            audit,
            audit_label,
        );
        (Some(Arc::new(dex)), http_loop)
    } else {
        let http = dexter_sentinel::request::client::build_client_for_cli(
            None,
            cli.proxy_url.as_deref(),
            true,
        )?;
        (None, http)
    };

    let cfg = TaxHarvestConfig {
        country_code: cc,
        tax_year: cli.tax_year,
        source_mode: cli.source_mode.into(),
        input_path: cli.input,
        source_url,
        insert,
        max_workers: cli.max_workers,
    };

    let summary = run_tax_harvest(&http, dexter, &cfg).await?;
    tracing::info!(
        "Tax harvest summary: {} source entries -> {} tax_types, {} tax_rates, {} tax_due_dates",
        summary.source_entries,
        summary.normalized_tax_types,
        summary.normalized_tax_rates,
        summary.normalized_tax_due_dates
    );
    if let Some(stats) = summary.insert_stats {
        tracing::info!(
            "Insert stats: tax_types ok={} dup={} fail={}, tax_rates ok={} dup={} fail={} skipped_missing_type={}, tax_due_dates ok={} dup={} fail={} skipped_missing_type={}",
            stats.tax_types_inserted,
            stats.tax_types_duplicates,
            stats.tax_types_failed,
            stats.tax_rates_inserted,
            stats.tax_rates_duplicates,
            stats.tax_rates_failed,
            stats.tax_rates_skipped_missing_type,
            stats.tax_due_dates_inserted,
            stats.tax_due_dates_duplicates,
            stats.tax_due_dates_failed,
            stats.tax_due_dates_skipped_missing_type
        );
    }
    Ok(())
}
