use actix_web::{web, HttpResponse};
use serde::Deserialize;
use serde_json::{json, Value};
use std::collections::HashMap;
use std::time::Instant;

use crate::proxy::config::ProxyConfig;
use crate::request::client::build_client;
use crate::request::headers::build_headers;
use crate::AppState;

#[derive(Deserialize)]
pub struct ScrapeRequest {
    pub config_name: String,
    pub url: String,
}

#[derive(Deserialize)]
pub struct ScrapeQuery {
    pub url: Option<String>,
}

pub async fn scrape_post_handler(
    state: web::Data<AppState>,
    body: web::Json<ScrapeRequest>,
) -> HttpResponse {
    run_scrape(&state, &body.config_name, &body.url).await
}

pub async fn scrape_get_handler(
    state: web::Data<AppState>,
    path: web::Path<String>,
    query: web::Query<ScrapeQuery>,
) -> HttpResponse {
    let config_name = path.into_inner();
    let url = match &query.url {
        Some(u) => u.clone(),
        None => {
            return HttpResponse::BadRequest().json(json!({"error": "url query param required"}));
        }
    };
    run_scrape(&state, &config_name, &url).await
}

async fn run_scrape(state: &AppState, config_name: &str, url: &str) -> HttpResponse {
    let start = Instant::now();

    let config = match state.scraper_configs.get(config_name) {
        Some(c) => c.clone(),
        None => {
            state
                .metrics
                .scrape_requests_total
                .with_label_values(&[config_name, "not_found"])
                .inc();
            return HttpResponse::NotFound()
                .json(json!({"error": format!("Config '{}' not found", config_name)}));
        }
    };

    // Check moka cache
    let cache_key: String = format!("{}:{}", config_name, url);
    if let Some(cached) = state.moka.get(&cache_key).await {
        state.metrics.cache_hits_total.inc();
        state
            .metrics
            .scrape_requests_total
            .with_label_values(&[config_name, "cache_hit"])
            .inc();
        if let Ok(val) = serde_json::from_str::<Value>(&cached) {
            return HttpResponse::Ok().json(val);
        }
    }

    // Build HTTP client
    let proxy: ProxyConfig = config.proxy.clone().unwrap_or(ProxyConfig::None);
    let timeout = config.request.timeout_secs.unwrap_or(30);
    let client = match build_client(&proxy, timeout) {
        Ok(c) => c,
        Err(e) => {
            tracing::error!("Failed to build HTTP client: {}", e);
            return HttpResponse::InternalServerError()
                .json(json!({"error": "Failed to build client"}));
        }
    };

    // Build request
    let mut req_builder = match config.request.method.to_uppercase().as_str() {
        "POST" => client.post(url),
        _ => client.get(url),
    };

    if let Some(ref headers) = config.request.headers {
        let header_map = build_headers(headers);
        req_builder = req_builder.headers(header_map);
    }

    // Execute request
    let response = match req_builder.send().await {
        Ok(r) => r,
        Err(e) => {
            tracing::error!("HTTP request failed: {}", e);
            state
                .metrics
                .scrape_requests_total
                .with_label_values(&[config_name, "error"])
                .inc();
            return HttpResponse::BadGateway()
                .json(json!({"error": format!("Request failed: {}", e)}));
        }
    };

    let html = match response.text().await {
        Ok(t) => t,
        Err(e) => {
            tracing::error!("Failed to read response: {}", e);
            return HttpResponse::InternalServerError()
                .json(json!({"error": "Failed to read response"}));
        }
    };

    // Parse HTML
    let document = scraper::Html::parse_document(&html);
    let mut result: HashMap<String, String> = HashMap::new();

    for selector_config in &config.selectors {
        let selector = match scraper::Selector::parse(&selector_config.css) {
            Ok(s) => s,
            Err(e) => {
                tracing::warn!("Invalid CSS selector '{}': {:?}", selector_config.css, e);
                continue;
            }
        };

        let element = document.select(&selector).next();
        let value = match element {
            Some(el) => {
                if selector_config.attribute == "text" {
                    el.text().collect::<Vec<_>>().join(" ")
                } else {
                    el.value()
                        .attr(&selector_config.attribute)
                        .unwrap_or("")
                        .to_string()
                }
            }
            None => {
                if selector_config.required.unwrap_or(false) {
                    tracing::warn!(
                        "Required selector '{}' not found on {}",
                        selector_config.name,
                        url
                    );
                }
                String::new()
            }
        };

        let value = match selector_config.transform.as_deref() {
            Some("trim") => value.trim().to_string(),
            Some("uppercase") => value.to_uppercase(),
            Some("lowercase") => value.to_lowercase(),
            _ => value,
        };

        result.insert(selector_config.name.clone(), value);
    }

    result.insert("url".to_string(), url.to_string());
    result.insert("config_name".to_string(), config_name.to_string());

    let duration = start.elapsed().as_secs_f64();
    state
        .metrics
        .scrape_duration_seconds
        .with_label_values(&[config_name])
        .observe(duration);
    state
        .metrics
        .scrape_requests_total
        .with_label_values(&[config_name, "success"])
        .inc();

    let response_json = json!(result);

    // Cache result
    state
        .moka
        .insert(cache_key, response_json.to_string())
        .await;

    // Persist to Athena if available
    if let Some(ref athena) = state.athena {
        let table = config
            .output
            .as_ref()
            .map(|o| o.table.as_str())
            .unwrap_or("scraped_data");
        if let Err(e) = athena
            .insert(table)
            .payload(response_json.clone())
            .execute()
            .await
        {
            tracing::warn!("Failed to persist scraped data: {}", e);
        }
    }

    HttpResponse::Ok().json(response_json)
}
