use std::path::PathBuf;

use anyhow::{bail, Context, Result};
use futures::future::BoxFuture;
use futures::FutureExt;
use serde_json::Value;

use super::config::{TaxHarvestConfig, TaxSourceMode};
use super::mapping::{parse_tax_input_document, TaxEntryInput};

pub trait TaxSourceAdapter: Send + Sync {
    fn name(&self) -> &'static str;
    fn fetch<'a>(&'a self, http: &'a reqwest::Client) -> BoxFuture<'a, Result<Vec<TaxEntryInput>>>;
}

pub struct CuratedJsonAdapter {
    input_path: PathBuf,
}

impl CuratedJsonAdapter {
    pub fn new(input_path: PathBuf) -> Self {
        Self { input_path }
    }
}

impl TaxSourceAdapter for CuratedJsonAdapter {
    fn name(&self) -> &'static str {
        "curated-json"
    }

    fn fetch<'a>(
        &'a self,
        _http: &'a reqwest::Client,
    ) -> BoxFuture<'a, Result<Vec<TaxEntryInput>>> {
        async move {
            let body = tokio::fs::read_to_string(&self.input_path)
                .await
                .with_context(|| {
                    format!("read curated json input '{}'", self.input_path.display())
                })?;
            let value: Value = serde_json::from_str(&body).context("parse curated JSON")?;
            parse_tax_input_document(value)
        }
        .boxed()
    }
}

pub struct WebTaxSourceAdapter {
    source_url: String,
}

impl WebTaxSourceAdapter {
    pub fn new(source_url: String) -> Self {
        Self { source_url }
    }
}

impl TaxSourceAdapter for WebTaxSourceAdapter {
    fn name(&self) -> &'static str {
        "web"
    }

    fn fetch<'a>(&'a self, http: &'a reqwest::Client) -> BoxFuture<'a, Result<Vec<TaxEntryInput>>> {
        async move {
            let resp = http
                .get(&self.source_url)
                .send()
                .await
                .with_context(|| format!("request tax source '{}'", self.source_url))?;
            if !resp.status().is_success() {
                bail!(
                    "web tax source returned HTTP {} for '{}'",
                    resp.status(),
                    self.source_url
                );
            }

            let body = resp.text().await.context("read tax source response body")?;
            let value: Value = serde_json::from_str(&body).with_context(|| {
                format!(
                    "web adapter scaffold currently expects a JSON endpoint; '{}' returned non-JSON",
                    self.source_url
                )
            })?;
            parse_tax_input_document(value)
        }
        .boxed()
    }
}

pub fn build_source_adapter(cfg: &TaxHarvestConfig) -> Result<Box<dyn TaxSourceAdapter>> {
    match cfg.source_mode {
        TaxSourceMode::CuratedJson => {
            let input = cfg.input_path.clone().ok_or_else(|| {
                anyhow::anyhow!("--input is required when --source-mode curated-json")
            })?;
            Ok(Box::new(CuratedJsonAdapter::new(input)))
        }
        TaxSourceMode::Web => {
            let source_url = cfg.source_url.clone().ok_or_else(|| {
                anyhow::anyhow!("--source-url is required when --source-mode web")
            })?;
            Ok(Box::new(WebTaxSourceAdapter::new(source_url)))
        }
    }
}

pub async fn fetch_tax_source_entries(
    http: &reqwest::Client,
    cfg: &TaxHarvestConfig,
) -> Result<Vec<TaxEntryInput>> {
    let adapter = build_source_adapter(cfg)?;
    tracing::info!("Tax source adapter: {}", adapter.name());
    adapter.fetch(http).await
}
