use std::collections::HashMap;
use std::time::SystemTime;

use serde::Deserialize;
use serde_json::Value;

use crate::analysis::{
    classify_query_params, detect_auth_from_headers, detect_field_roles, find_array_path,
    flatten_fields, infer_capability_name, infer_strategy,
};
use crate::constants::is_noise_url;

use super::{AnalyzedEndpoint, InferredCapability, RecommendedArg};

// ---------------------------------------------------------------------------
// Network entry parsing
// ---------------------------------------------------------------------------

/// A single network request/response captured by the interceptor.
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub(super) struct NetworkEntry {
    pub(super) method: String,
    pub(super) url: String,
    pub(super) status: Option<u16>,
    pub(super) content_type: Option<String>,
    pub(super) response_body: Option<String>,
    #[serde(default)]
    pub(super) request_headers: HashMap<String, String>,
}

/// Analyze captured network entries into deduplicated, scored endpoints.
///
/// Filters out noise URLs and non-JSON responses, deduplicates by URL pattern,
/// and analyzes response bodies for array structures and field roles.
/// Results are sorted by usefulness (item_count * 10 + detected_fields.len()).
pub(super) fn analyze_endpoints(entries: &[NetworkEntry]) -> Vec<AnalyzedEndpoint> {
    let mut seen_patterns = HashMap::<String, usize>::new();
    let mut endpoints = Vec::new();

    for entry in entries {
        // Skip noise and non-JSON
        if is_noise_url(&entry.url) {
            continue;
        }
        let ct = entry.content_type.as_deref().unwrap_or("");
        if !ct.contains("json") {
            continue;
        }

        let pattern = crate::analysis::url_to_pattern(&entry.url);

        // Deduplicate by pattern
        if seen_patterns.contains_key(&pattern) {
            continue;
        }
        seen_patterns.insert(pattern.clone(), endpoints.len());

        // Classify query params
        let param_cls = classify_query_params(&entry.url);

        // Detect auth from request headers
        let auth_indicators = detect_auth_from_headers(&entry.request_headers);

        // Analyze response body
        let mut item_path = None;
        let mut item_count = 0;
        let mut detected_fields = HashMap::new();

        if let Some(body_text) = &entry.response_body {
            if let Ok(json) = serde_json::from_str::<Value>(body_text) {
                if let Some((path, items)) = find_array_path(&json, 5) {
                    item_path = Some(path);
                    item_count = items.len();

                    // Flatten fields from first item
                    if let Some(first) = items.first() {
                        let fields = flatten_fields(first, "", 3);
                        detected_fields = detect_field_roles(&fields);
                    }
                }
            }
        }

        endpoints.push(AnalyzedEndpoint {
            pattern,
            method: entry.method.clone(),
            url: entry.url.clone(),
            status: entry.status,
            content_type: ct.to_string(),
            query_params: param_cls.params,
            has_search_param: param_cls.has_search,
            has_pagination_param: param_cls.has_pagination,
            has_limit_param: param_cls.has_limit,
            auth_indicators,
            item_path,
            item_count,
            detected_fields,
        });
    }

    // Sort by usefulness: item_count * 10 + detected_fields.len(), descending
    endpoints.sort_by(|a, b| {
        let score_a = a.item_count * 10 + a.detected_fields.len();
        let score_b = b.item_count * 10 + b.detected_fields.len();
        score_b.cmp(&score_a)
    });

    endpoints
}

// ---------------------------------------------------------------------------
// Capability inference
// ---------------------------------------------------------------------------

/// Infer capabilities from the top analyzed endpoints.
///
/// Takes the top 8 endpoints and generates capabilities with recommended
/// arguments (keyword if search param, limit always, page if pagination).
pub(super) fn infer_capabilities(
    endpoints: &[AnalyzedEndpoint],
    site: &str,
    goal: Option<&str>,
) -> (Vec<InferredCapability>, String, Vec<String>) {
    let top = &endpoints[..endpoints.len().min(8)];
    let mut capabilities = Vec::new();
    let mut all_auth: Vec<String> = Vec::new();

    for ep in top {
        all_auth.extend(ep.auth_indicators.clone());

        let name = infer_capability_name(&ep.url, goal);
        let strategy = infer_strategy(&ep.auth_indicators);

        // Build recommended columns from detected field roles
        let mut columns: Vec<String> = ep.detected_fields.keys().cloned().collect();
        columns.sort();

        // Build recommended args
        let mut args = Vec::new();

        if ep.has_search_param {
            args.push(RecommendedArg {
                name: "keyword".to_string(),
                arg_type: "String".to_string(),
                required: true,
                default: None,
            });
        }

        args.push(RecommendedArg {
            name: "limit".to_string(),
            arg_type: "u32".to_string(),
            required: false,
            default: Some(Value::Number(20.into())),
        });

        if ep.has_pagination_param {
            args.push(RecommendedArg {
                name: "page".to_string(),
                arg_type: "u32".to_string(),
                required: false,
                default: Some(Value::Number(1.into())),
            });
        }

        let description = format!("{} {} from {}", name, "data", site);

        capabilities.push(InferredCapability {
            name,
            description,
            strategy,
            endpoint: ep.pattern.clone(),
            item_path: ep.item_path.clone(),
            recommended_columns: columns,
            recommended_args: args,
        });
    }

    // Deduplicate auth indicators
    all_auth.sort();
    all_auth.dedup();

    // Determine top strategy from the first capability or fallback
    let top_strategy = capabilities
        .first()
        .map(|c| c.strategy.clone())
        .unwrap_or_else(|| "public".to_string());

    (capabilities, top_strategy, all_auth)
}

// ---------------------------------------------------------------------------
// Known site aliases
// ---------------------------------------------------------------------------

pub(super) const KNOWN_SITE_ALIASES: &[(&str, &str)] = &[
    ("x.com", "twitter"),
    ("twitter.com", "twitter"),
    ("news.ycombinator.com", "hackernews"),
    ("www.zhihu.com", "zhihu"),
    ("www.bilibili.com", "bilibili"),
    ("search.bilibili.com", "bilibili"),
    ("www.v2ex.com", "v2ex"),
    ("www.reddit.com", "reddit"),
    ("www.xiaohongshu.com", "xiaohongshu"),
    ("www.douban.com", "douban"),
    ("www.weibo.com", "weibo"),
];

/// Detect a human-friendly site name from a URL.
///
/// Checks known aliases first, then falls back to the second-level domain.
pub fn detect_site_name(url: &str) -> String {
    if let Ok(parsed) = url::Url::parse(url) {
        if let Some(host) = parsed.host_str() {
            // Check known aliases
            for (domain, name) in KNOWN_SITE_ALIASES {
                if host == *domain {
                    return name.to_string();
                }
            }

            // Fallback: extract meaningful domain name
            // Handle two-part TLDs like .com.tw, .co.uk, .com.cn
            let parts: Vec<&str> = host
                .split('.')
                .filter(|p| !p.is_empty() && *p != "www")
                .collect();
            let two_part_tlds = [
                "com.tw", "co.uk", "com.cn", "com.hk", "co.jp", "com.au", "co.kr",
            ];
            let is_two_part_tld = two_part_tlds.iter().any(|tld| host.ends_with(tld));
            if is_two_part_tld && parts.len() >= 3 {
                return parts[parts.len() - 3].to_string();
            }
            if parts.len() >= 2 {
                return parts[parts.len() - 2].to_string();
            }
            return host.to_string();
        }
    }
    "unknown".to_string()
}

// ---------------------------------------------------------------------------
// Timestamp helper
// ---------------------------------------------------------------------------

pub(super) fn epoch_seconds() -> u64 {
    SystemTime::now()
        .duration_since(SystemTime::UNIX_EPOCH)
        .unwrap_or_default()
        .as_secs()
}
