use std::cmp::Ordering;
use std::collections::HashSet;

use chrono::NaiveDate;

use crate::terminal::{FilingListItem, FilingRef, FilingsPanelData};

use super::client::SecEdgarClient;
use super::facts::company_name;
use super::service::EdgarLookupError;
use super::types::{CompanySubmissions, FilingsQuery};

const SEC_ARCHIVE_PREFIX: &str = "https://www.sec.gov/Archives/edgar/data";

pub(crate) async fn build_panel_data(
    client: &SecEdgarClient,
    ticker: &str,
    query: &FilingsQuery,
) -> Result<FilingsPanelData, EdgarLookupError> {
    let company = client.resolve_company(ticker).await?;
    let submissions = client.load_submissions(&company.cik).await?;
    let filings = load_all_filings(client, &submissions).await?;
    let latest_filing = filings
        .first()
        .map(|filing| filing_list_item(&company.cik, filing));
    let filtered = filter_filings(&filings, query.query.as_deref());
    let total_results = filtered.len();
    let total_pages = total_pages(total_results, query.page_size);
    let page = query.page.min(total_pages);
    let page_start = (page - 1) * query.page_size;
    let items = filtered
        .into_iter()
        .skip(page_start)
        .take(query.page_size)
        .map(|filing| filing_list_item(&company.cik, filing))
        .collect();

    Ok(FilingsPanelData {
        symbol: company.ticker.clone(),
        company_name: company_name(&company, Some(&submissions.name), None),
        cik: company.cik,
        items,
        page,
        page_size: query.page_size,
        total_results,
        total_pages,
        query: normalize_query(query.query.as_deref()),
        latest_filing,
    })
}

async fn load_all_filings(
    client: &SecEdgarClient,
    submissions: &CompanySubmissions,
) -> Result<Vec<FilingRef>, EdgarLookupError> {
    let mut filings = submissions.filings.recent.rows();

    for file in &submissions.filings.files {
        let supplemental = client.load_submissions_file(&file.name).await?;
        filings.extend(supplemental.rows());
    }

    filings.sort_by(compare_filing_history);

    let mut seen_accessions = HashSet::new();
    filings.retain(|filing| seen_accessions.insert(filing.accession_number.clone()));

    Ok(filings)
}

fn filter_filings<'a>(filings: &'a [FilingRef], query: Option<&str>) -> Vec<&'a FilingRef> {
    let Some(query) = normalize_query(query) else {
        return filings.iter().collect();
    };
    let normalized_query = query.to_ascii_lowercase();

    filings
        .iter()
        .filter(|filing| filing_matches_query(filing, &normalized_query))
        .collect()
}

fn filing_matches_query(filing: &FilingRef, query: &str) -> bool {
    [
        filing.form.as_str(),
        filing.filing_date.as_str(),
        filing.report_date.as_deref().unwrap_or_default(),
        filing
            .primary_document
            .as_deref()
            .map(str::trim)
            .unwrap_or_default(),
        filing.accession_number.as_str(),
    ]
    .into_iter()
    .any(|field| field.to_ascii_lowercase().contains(query))
}

fn filing_list_item(cik: &str, filing: &FilingRef) -> FilingListItem {
    let primary_document = filing
        .primary_document
        .as_deref()
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .unwrap_or_default()
        .to_string();

    FilingListItem {
        accession_number: filing.accession_number.clone(),
        filing_date: filing.filing_date.clone(),
        report_date: filing.report_date.clone(),
        form: filing.form.clone(),
        filing_url: build_filing_url(
            cik,
            &filing.accession_number,
            (!primary_document.is_empty()).then_some(primary_document.as_str()),
        ),
        primary_document,
        overview: filing_overview(&filing.form),
    }
}

fn build_filing_url(cik: &str, accession_number: &str, primary_document: Option<&str>) -> String {
    let normalized_cik = archive_cik(cik);
    let normalized_accession = accession_number.replace('-', "");

    match primary_document.filter(|value| !value.trim().is_empty()) {
        Some(primary_document) => format!(
            "{SEC_ARCHIVE_PREFIX}/{normalized_cik}/{normalized_accession}/{primary_document}"
        ),
        None => format!("{SEC_ARCHIVE_PREFIX}/{normalized_cik}/{normalized_accession}/"),
    }
}

fn archive_cik(cik: &str) -> &str {
    let trimmed = cik.trim_start_matches('0');
    if trimmed.is_empty() {
        cik
    } else {
        trimmed
    }
}

fn filing_overview(form: &str) -> String {
    let is_amendment = form.ends_with("/A");
    let base_form = form.strip_suffix("/A").unwrap_or(form);
    let summary = match base_form {
        "10-K" => "Annual report",
        "10-Q" => "Quarterly report",
        "8-K" => "Current report",
        "20-F" => "Foreign private issuer annual report",
        "6-K" => "Foreign private issuer current report",
        "40-F" => "Canadian issuer annual report",
        _ => "SEC filing",
    };

    if is_amendment {
        format!("{summary} amendment")
    } else {
        summary.to_string()
    }
}

fn compare_filing_history(left: &FilingRef, right: &FilingRef) -> Ordering {
    compare_date_strings(Some(&left.filing_date), Some(&right.filing_date))
        .then_with(|| {
            compare_date_strings(left.report_date.as_deref(), right.report_date.as_deref())
        })
        .then_with(|| amendment_priority(&left.form).cmp(&amendment_priority(&right.form)))
        .then_with(|| right.accession_number.cmp(&left.accession_number))
}

fn compare_date_strings(left: Option<&str>, right: Option<&str>) -> Ordering {
    let left_date = left.and_then(parse_date);
    let right_date = right.and_then(parse_date);
    left_date.cmp(&right_date).reverse()
}

fn parse_date(value: &str) -> Option<NaiveDate> {
    NaiveDate::parse_from_str(value, "%Y-%m-%d").ok()
}

fn amendment_priority(form: &str) -> u8 {
    u8::from(form.ends_with("/A"))
}

fn normalize_query(query: Option<&str>) -> Option<String> {
    query
        .map(str::trim)
        .filter(|value| !value.is_empty())
        .map(ToOwned::to_owned)
}

fn total_pages(total_results: usize, page_size: usize) -> usize {
    usize::max(1, total_results.div_ceil(page_size))
}

#[cfg(test)]
mod tests {
    use std::collections::HashMap;

    use futures::future::BoxFuture;

    use super::*;
    use crate::terminal::sec_edgar::client::SecFetch;

    const TICKERS_URL: &str = "https://www.sec.gov/files/company_tickers.json";
    const SUBMISSIONS_URL: &str = "https://data.sec.gov/submissions/CIK0001234567.json";
    const SUPPLEMENTAL_URL: &str = "https://data.sec.gov/submissions/companyfilings-old.json";

    struct FixtureFetcher {
        text: HashMap<String, String>,
    }

    impl SecFetch for FixtureFetcher {
        fn get_text<'a>(&'a self, url: &'a str) -> BoxFuture<'a, Result<String, EdgarLookupError>> {
            Box::pin(async move {
                self.text
                    .get(url)
                    .cloned()
                    .ok_or_else(|| EdgarLookupError::RequestFailed {
                        provider: "SEC EDGAR",
                        detail: format!("missing fixture for {url}"),
                    })
            })
        }

        fn get_bytes<'a>(
            &'a self,
            _url: &'a str,
        ) -> BoxFuture<'a, Result<Vec<u8>, EdgarLookupError>> {
            Box::pin(async move {
                Err(EdgarLookupError::RequestFailed {
                    provider: "SEC EDGAR",
                    detail: "bytes fixture not configured".to_string(),
                })
            })
        }
    }

    fn client() -> SecEdgarClient {
        let mut text = HashMap::new();
        text.insert(
            TICKERS_URL.to_string(),
            serde_json::json!({
                "0": {
                    "cik_str": 1234567,
                    "ticker": "FIL",
                    "title": "Filings Test Co."
                }
            })
            .to_string(),
        );
        text.insert(
            SUBMISSIONS_URL.to_string(),
            serde_json::json!({
                "name": "Filings Test Co.",
                "filings": {
                    "recent": {
                        "accessionNumber": [
                            "0001234567-25-000003",
                            "0001234567-25-000002"
                        ],
                        "filingDate": [
                            "2025-03-01",
                            "2025-02-01"
                        ],
                        "reportDate": [
                            "2024-12-31",
                            "2024-12-31"
                        ],
                        "form": [
                            "10-K/A",
                            "10-K"
                        ],
                        "primaryDocument": [
                            "annual-amendment.htm",
                            "annual.htm"
                        ]
                    },
                    "files": [
                        {
                            "name": "companyfilings-old.json"
                        }
                    ]
                }
            })
            .to_string(),
        );
        text.insert(
            SUPPLEMENTAL_URL.to_string(),
            serde_json::json!({
                "accessionNumber": [
                    "0001234567-25-000002",
                    "0001234567-24-000001",
                    "0001234567-24-000000",
                    "0001234567-24-000009"
                ],
                "filingDate": [
                    "2025-02-01",
                    "2024-11-01",
                    "2024-11-01",
                    "2024-10-15"
                ],
                "reportDate": [
                    "2024-12-31",
                    "2024-09-28",
                    "2024-09-28",
                    null
                ],
                "form": [
                    "10-K",
                    "10-K",
                    "10-K/A",
                    "8-K"
                ],
                "primaryDocument": [
                    "annual.htm",
                    "older-annual.htm",
                    "older-annual-amendment.htm",
                    ""
                ]
            })
            .to_string(),
        );

        SecEdgarClient::new(Box::new(FixtureFetcher { text }))
    }

    #[tokio::test]
    async fn build_panel_data_should_merge_sort_and_deduplicate_recent_and_supplemental_filings() {
        let panel = build_panel_data(
            &client(),
            "FIL",
            &FilingsQuery {
                page: 1,
                page_size: 25,
                query: None,
            },
        )
        .await
        .unwrap();

        let accession_numbers = panel
            .items
            .iter()
            .map(|item| item.accession_number.as_str())
            .collect::<Vec<_>>();

        assert_eq!(panel.symbol, "FIL");
        assert_eq!(panel.total_results, 5);
        assert_eq!(
            accession_numbers,
            vec![
                "0001234567-25-000003",
                "0001234567-25-000002",
                "0001234567-24-000001",
                "0001234567-24-000000",
                "0001234567-24-000009",
            ]
        );
        assert_eq!(
            panel
                .latest_filing
                .as_ref()
                .map(|filing| filing.accession_number.as_str()),
            Some("0001234567-25-000003")
        );
    }

    #[test]
    fn compare_filing_history_should_prioritize_newer_filing_dates_then_original_forms() {
        let mut filings = vec![
            FilingRef {
                accession_number: "0003".to_string(),
                filing_date: "2025-01-10".to_string(),
                report_date: Some("2024-12-31".to_string()),
                form: "10-K/A".to_string(),
                primary_document: None,
            },
            FilingRef {
                accession_number: "0004".to_string(),
                filing_date: "2025-01-10".to_string(),
                report_date: Some("2024-12-31".to_string()),
                form: "10-K".to_string(),
                primary_document: None,
            },
            FilingRef {
                accession_number: "0002".to_string(),
                filing_date: "2025-01-10".to_string(),
                report_date: Some("2024-09-30".to_string()),
                form: "10-Q".to_string(),
                primary_document: None,
            },
            FilingRef {
                accession_number: "0001".to_string(),
                filing_date: "2024-12-01".to_string(),
                report_date: Some("2024-09-30".to_string()),
                form: "10-Q".to_string(),
                primary_document: None,
            },
        ];

        filings.sort_by(compare_filing_history);

        let accession_numbers = filings
            .iter()
            .map(|filing| filing.accession_number.as_str())
            .collect::<Vec<_>>();

        assert_eq!(accession_numbers, vec!["0004", "0003", "0002", "0001"]);
    }

    #[test]
    fn filter_filings_should_match_metadata_fields_only() {
        let filings = vec![
            FilingRef {
                accession_number: "0001".to_string(),
                filing_date: "2025-02-01".to_string(),
                report_date: Some("2024-12-31".to_string()),
                form: "10-K".to_string(),
                primary_document: Some("annual.htm".to_string()),
            },
            FilingRef {
                accession_number: "0002".to_string(),
                filing_date: "2025-01-15".to_string(),
                report_date: None,
                form: "8-K".to_string(),
                primary_document: None,
            },
        ];

        assert_eq!(filter_filings(&filings, Some("10-k")).len(), 1);
        assert_eq!(filter_filings(&filings, Some("2024-12-31")).len(), 1);
        assert_eq!(filter_filings(&filings, Some("annual.htm")).len(), 1);
        assert_eq!(filter_filings(&filings, Some("annual report")).len(), 0);
    }

    #[test]
    fn build_filing_url_should_prefer_primary_document_and_fallback_to_directory() {
        assert_eq!(
            build_filing_url("0001234567", "0001234567-25-000002", Some("annual.htm"),),
            "https://www.sec.gov/Archives/edgar/data/1234567/000123456725000002/annual.htm"
        );
        assert_eq!(
            build_filing_url("0001234567", "0001234567-25-000002", None),
            "https://www.sec.gov/Archives/edgar/data/1234567/000123456725000002/"
        );
    }

    #[test]
    fn filing_overview_should_map_common_forms_and_fallback_for_unknown_forms() {
        assert_eq!(filing_overview("10-K"), "Annual report");
        assert_eq!(filing_overview("10-Q"), "Quarterly report");
        assert_eq!(filing_overview("8-K"), "Current report");
        assert_eq!(
            filing_overview("20-F"),
            "Foreign private issuer annual report"
        );
        assert_eq!(
            filing_overview("6-K/A"),
            "Foreign private issuer current report amendment"
        );
        assert_eq!(filing_overview("424B2"), "SEC filing");
    }
}
