//! FRED (US macro / economy) endpoints: series observations + series search.

use motosan_finance::{EconomyFredData, EconomyFredSeriesInfo, FinanceResult};
use serde::Serialize;

use crate::client::FinProxy;
use crate::error::SdkError;

/// `?series_id=GDP&start=&end=` — observations for a FRED series.
#[derive(Debug, Clone, Serialize, Default)]
pub struct SeriesParams {
    pub series_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub end: Option<String>,
}

impl SeriesParams {
    pub fn new(series_id: impl Into<String>) -> Self {
        Self { series_id: series_id.into(), ..Default::default() }
    }
    pub fn start(mut self, d: impl Into<String>) -> Self {
        self.start = Some(d.into());
        self
    }
    pub fn end(mut self, d: impl Into<String>) -> Self {
        self.end = Some(d.into());
        self
    }
}

/// `?query=inflation&limit=20` — search FRED series by text.
#[derive(Debug, Clone, Serialize, Default)]
pub struct SearchParams {
    pub query: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl SearchParams {
    pub fn new(query: impl Into<String>) -> Self {
        Self { query: query.into(), ..Default::default() }
    }
    pub fn limit(mut self, n: u32) -> Self {
        self.limit = Some(n);
        self
    }
}

/// FRED sub-client, obtained via [`FinProxy::fred`].
pub struct Fred<'a> {
    pub(crate) client: &'a FinProxy,
}

impl FinProxy {
    /// Access the FRED (US macro / economy) endpoints.
    pub fn fred(&self) -> Fred<'_> {
        Fred { client: self }
    }
}

impl Fred<'_> {
    /// GET /v1/fred/series
    pub async fn series(
        &self,
        params: &SeriesParams,
    ) -> Result<FinanceResult<Vec<EconomyFredData>>, SdkError> {
        self.client.get("/v1/fred/series", params).await
    }

    /// GET /v1/fred/search
    pub async fn search(
        &self,
        params: &SearchParams,
    ) -> Result<FinanceResult<Vec<EconomyFredSeriesInfo>>, SdkError> {
        self.client.get("/v1/fred/search", params).await
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use wiremock::matchers::{method, path, query_param};
    use wiremock::{Mock, MockServer, ResponseTemplate};

    #[tokio::test]
    async fn series_hits_path_and_encodes_dates() {
        let server = MockServer::start().await;
        // Empty `results` avoids putting a Decimal in the fixture; this verifies the
        // path, the start/end query encoding, and envelope unwrap. Populated-row
        // deserialization is covered by the live check in the review.
        let body = r#"{"success":true,"data":{"results":[],"provider":"fred",
            "warnings":[],"extra":{}},"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/fred/series"))
            .and(query_param("series_id", "GDP"))
            .and(query_param("start", "2020-01-01"))
            .and(query_param("end", "2024-01-01"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let res = client
            .fred()
            .series(&SeriesParams::new("GDP").start("2020-01-01").end("2024-01-01"))
            .await
            .unwrap();
        assert_eq!(res.provider.as_deref(), Some("fred"));
    }

    #[tokio::test]
    async fn search_parses_series_info_row() {
        let server = MockServer::start().await;
        // EconomyFredSeriesInfo is all strings/options (no Decimal), so a populated
        // row is safe here and proves real deserialization. Optional fields omitted
        // (serde maps absent Option fields to None).
        let body = r#"{"success":true,"data":{"results":[
            {"id":"GDP","title":"Gross Domestic Product","frequency":"Quarterly"}
            ],"provider":"fred","warnings":[],"extra":{}},"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/fred/search"))
            .and(query_param("query", "gdp"))
            .and(query_param("limit", "5"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let res = client
            .fred()
            .search(&SearchParams::new("gdp").limit(5))
            .await
            .unwrap();
        let rows = res.results.unwrap();
        assert_eq!(rows.len(), 1);
        assert_eq!(rows[0].id, "GDP");
        assert_eq!(rows[0].frequency.as_deref(), Some("Quarterly"));
    }
}
