//! SEC EDGAR (US filings + insider transactions) endpoints. No API key required.

use motosan_finance::{EquityProfileData, FinanceResult};
use motosan_finance_sec::{EpsHistory, InsiderTransaction, SecFiling};
use serde::Serialize;

use crate::client::FinProxy;
use crate::error::SdkError;
// Profile reuses the shared symbol-only params.
pub use crate::equity::SymbolParams;

/// `?symbol=&form_type=&limit=` for recent filings.
#[derive(Debug, Clone, Serialize, Default)]
pub struct FilingsParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub form_type: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

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

/// `?symbol=&limit=` for Form 4 insider transactions.
#[derive(Debug, Clone, Serialize, Default)]
pub struct InsiderParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

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

/// `?symbol=&form=&concept=&namespace=&limit=` for diluted-EPS history.
#[derive(Debug, Clone, Serialize, Default)]
pub struct EpsHistoryParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub form: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub concept: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub namespace: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub limit: Option<u32>,
}

impl EpsHistoryParams {
    pub fn new(symbol: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), ..Default::default() }
    }
    pub fn form(mut self, f: impl Into<String>) -> Self {
        self.form = Some(f.into());
        self
    }
    pub fn concept(mut self, c: impl Into<String>) -> Self {
        self.concept = Some(c.into());
        self
    }
    pub fn namespace(mut self, n: impl Into<String>) -> Self {
        self.namespace = Some(n.into());
        self
    }
    pub fn limit(mut self, n: u32) -> Self {
        self.limit = Some(n);
        self
    }
}

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

impl FinProxy {
    /// Access the SEC EDGAR (US filings + insider transactions) endpoints.
    pub fn sec(&self) -> Sec<'_> {
        Sec { client: self }
    }
}

impl Sec<'_> {
    /// GET /v1/sec/equity/profile
    pub async fn profile(
        &self,
        params: &SymbolParams,
    ) -> Result<FinanceResult<Vec<EquityProfileData>>, SdkError> {
        self.client.get("/v1/sec/equity/profile", params).await
    }

    /// GET /v1/sec/filings
    pub async fn filings(&self, params: &FilingsParams) -> Result<Vec<SecFiling>, SdkError> {
        self.client.get("/v1/sec/filings", params).await
    }

    /// GET /v1/sec/insider-transactions
    pub async fn insider_transactions(
        &self,
        params: &InsiderParams,
    ) -> Result<Vec<InsiderTransaction>, SdkError> {
        self.client.get("/v1/sec/insider-transactions", params).await
    }

    /// GET /v1/sec/eps-history
    pub async fn eps_history(&self, params: &EpsHistoryParams) -> Result<EpsHistory, SdkError> {
        self.client.get("/v1/sec/eps-history", params).await
    }
}

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

    // `data` is kept EMPTY in every test: SecFiling / InsiderTransaction have
    // required fields, so a partial row would fail to deserialize. These tests
    // verify (a) the path, (b) query-param encoding, and (c) envelope unwrap.

    #[tokio::test]
    async fn filings_encodes_form_type_and_limit_and_unwraps_envelope() {
        let server = MockServer::start().await;
        let body = r#"{"success":true,"data":[],"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/sec/filings"))
            .and(query_param("symbol", "AAPL"))
            .and(query_param("form_type", "10-K"))
            .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 rows = client
            .sec()
            .filings(&FilingsParams::new("AAPL").form_type("10-K").limit(5))
            .await
            .unwrap();
        assert!(rows.is_empty());
    }

    #[tokio::test]
    async fn filings_omits_absent_optional_params() {
        let server = MockServer::start().await;
        let body = r#"{"success":true,"data":[],"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/sec/filings"))
            .and(query_param("symbol", "AAPL"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let rows = client.sec().filings(&FilingsParams::new("AAPL")).await.unwrap();
        assert!(rows.is_empty());
    }

    #[tokio::test]
    async fn insider_transactions_encodes_limit_and_unwraps_envelope() {
        let server = MockServer::start().await;
        let body = r#"{"success":true,"data":[],"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/sec/insider-transactions"))
            .and(query_param("symbol", "AAPL"))
            .and(query_param("limit", "10"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let rows = client
            .sec()
            .insider_transactions(&InsiderParams::new("AAPL").limit(10))
            .await
            .unwrap();
        assert!(rows.is_empty());
    }

    #[tokio::test]
    async fn profile_unwraps_finance_result() {
        let server = MockServer::start().await;
        let body = r#"{"success":true,"data":{"results":[],"provider":"sec","warnings":[],"extra":{}},"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/sec/equity/profile"))
            .and(query_param("symbol", "AAPL"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let res = client.sec().profile(&SymbolParams::new("AAPL")).await.unwrap();
        assert_eq!(res.provider.as_deref(), Some("sec"));
    }

    #[tokio::test]
    async fn eps_history_encodes_params_and_unwraps_object() {
        let server = MockServer::start().await;
        // EpsHistory is a single object with required fields; `quarters` kept empty so
        // we never need EpsQuarter fields. Verifies path + param encoding + unwrap.
        let body = r#"{"success":true,"data":{"symbol":"AAPL","cik":"0000320193",
            "entity_name":"Apple Inc.","concept":"EarningsPerShareDiluted","namespace":"us-gaap",
            "unit":"USD/shares","quarters":[],"fetched_at":"2024-01-01T00:00:00Z"},"meta":{}}"#;
        Mock::given(method("GET"))
            .and(path("/v1/sec/eps-history"))
            .and(query_param("symbol", "AAPL"))
            .and(query_param("form", "10-K"))
            .and(query_param("limit", "8"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body))
            .mount(&server)
            .await;

        let client = FinProxy::new(server.uri()).unwrap();
        let eps = client
            .sec()
            .eps_history(&EpsHistoryParams::new("AAPL").form("10-K").limit(8))
            .await
            .unwrap();
        assert_eq!(eps.symbol, "AAPL");
        assert!(eps.quarters.is_empty());
    }
}
