//! galaxyark (proprietary) endpoints: Ark, Owl2, Owl5.
//!
//! Nested access: `client.galaxyark().ark()` / `.owl2()` / `.owl5()`.
//! Results are passed through as `serde_json::Value` (the server serializes the
//! raw result — `Vec<Kbar>`, `ArkTable`, single objects, etc. — to `Value`).
//! Provider credentials are configured server-side; a missing one yields 503.

use serde::Serialize;
use serde_json::Value;

use crate::client::FinProxy;
use crate::error::SdkError;
// Single-symbol endpoints reuse the shared equity param.
pub use crate::equity::SymbolParams;

/// `?symbols=2330,2317` (comma-separated).
#[derive(Debug, Clone, Serialize)]
pub struct SymbolsParams {
    pub symbols: String,
}
impl SymbolsParams {
    pub fn new(symbols: impl Into<String>) -> Self {
        Self { symbols: symbols.into() }
    }
}

/// `?symbol=&minutes=` (minutes optional: 1|5|15|30|60).
#[derive(Debug, Clone, Serialize, Default)]
pub struct KbarParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub minutes: Option<u32>,
}
impl KbarParams {
    pub fn new(symbol: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), ..Default::default() }
    }
    pub fn minutes(mut self, m: u32) -> Self {
        self.minutes = Some(m);
        self
    }
}

/// `?symbol=&minutes=&days=`.
#[derive(Debug, Clone, Serialize, Default)]
pub struct RecentKbarParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub minutes: Option<u32>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub days: Option<u32>,
}
impl RecentKbarParams {
    pub fn new(symbol: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), ..Default::default() }
    }
    pub fn minutes(mut self, m: u32) -> Self {
        self.minutes = Some(m);
        self
    }
    pub fn days(mut self, d: u32) -> Self {
        self.days = Some(d);
        self
    }
}

/// `?minutes=` (TXF index-futures bars).
#[derive(Debug, Clone, Serialize, Default)]
pub struct MinutesParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub minutes: Option<u32>,
}
impl MinutesParams {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn minutes(mut self, m: u32) -> Self {
        self.minutes = Some(m);
        self
    }
}

/// `?symbol=&numbers=`.
#[derive(Debug, Clone, Serialize, Default)]
pub struct NewsParams {
    pub symbol: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub numbers: Option<u32>,
}
impl NewsParams {
    pub fn new(symbol: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), ..Default::default() }
    }
    pub fn numbers(mut self, n: u32) -> Self {
        self.numbers = Some(n);
        self
    }
}

/// `?symbol=&start=&end=&days=` (start/end REQUIRED).
#[derive(Debug, Clone, Serialize)]
pub struct BrokerParams {
    pub symbol: String,
    pub start: String,
    pub end: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub days: Option<u32>,
}
impl BrokerParams {
    pub fn new(symbol: impl Into<String>, start: impl Into<String>, end: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), start: start.into(), end: end.into(), days: None }
    }
    pub fn days(mut self, d: u32) -> Self {
        self.days = Some(d);
        self
    }
}

/// `?numbers=`.
#[derive(Debug, Clone, Serialize, Default)]
pub struct NumbersParams {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub numbers: Option<u32>,
}
impl NumbersParams {
    pub fn new() -> Self {
        Self::default()
    }
    pub fn numbers(mut self, n: u32) -> Self {
        self.numbers = Some(n);
        self
    }
}

/// `?table_id=&symbol=&numbers=` for the owl5 data-table endpoints (all required).
#[derive(Debug, Clone, Serialize)]
pub struct DataTableParams {
    pub table_id: String,
    pub symbol: String,
    pub numbers: u32,
}
impl DataTableParams {
    pub fn new(table_id: impl Into<String>, symbol: impl Into<String>, numbers: u32) -> Self {
        Self { table_id: table_id.into(), symbol: symbol.into(), numbers }
    }
}

/// `?coid=` (warrant id).
#[derive(Debug, Clone, Serialize)]
pub struct CoidParams {
    pub coid: String,
}
impl CoidParams {
    pub fn new(coid: impl Into<String>) -> Self {
        Self { coid: coid.into() }
    }
}

/// `?index_id=`.
#[derive(Debug, Clone, Serialize)]
pub struct IndexIdParams {
    pub index_id: String,
}
impl IndexIdParams {
    pub fn new(index_id: impl Into<String>) -> Self {
        Self { index_id: index_id.into() }
    }
}

/// `?type_name=`.
#[derive(Debug, Clone, Serialize)]
pub struct TypeNameParams {
    pub type_name: String,
}
impl TypeNameParams {
    pub fn new(type_name: impl Into<String>) -> Self {
        Self { type_name: type_name.into() }
    }
}

/// `?index=&numbers=`.
#[derive(Debug, Clone, Serialize, Default)]
pub struct EconIndexParams {
    pub index: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub numbers: Option<u32>,
}
impl EconIndexParams {
    pub fn new(index: impl Into<String>) -> Self {
        Self { index: index.into(), ..Default::default() }
    }
    pub fn numbers(mut self, n: u32) -> Self {
        self.numbers = Some(n);
        self
    }
}

/// `?broker_id=`.
#[derive(Debug, Clone, Serialize)]
pub struct BrokerIdParams {
    pub broker_id: String,
}
impl BrokerIdParams {
    pub fn new(broker_id: impl Into<String>) -> Self {
        Self { broker_id: broker_id.into() }
    }
}

/// `?broker_id=&days=`.
#[derive(Debug, Clone, Serialize, Default)]
pub struct BrokerDaysParams {
    pub broker_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub days: Option<u32>,
}
impl BrokerDaysParams {
    pub fn new(broker_id: impl Into<String>) -> Self {
        Self { broker_id: broker_id.into(), ..Default::default() }
    }
    pub fn days(mut self, d: u32) -> Self {
        self.days = Some(d);
        self
    }
}

/// `?symbol=&broker_id=&start=` (start optional).
#[derive(Debug, Clone, Serialize, Default)]
pub struct BrokerDailyParams {
    pub symbol: String,
    pub broker_id: String,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub start: Option<String>,
}
impl BrokerDailyParams {
    pub fn new(symbol: impl Into<String>, broker_id: impl Into<String>) -> Self {
        Self { symbol: symbol.into(), broker_id: broker_id.into(), start: None }
    }
    pub fn start(mut self, d: impl Into<String>) -> Self {
        self.start = Some(d.into());
        self
    }
}

/// Empty query for parameterless endpoints (serializes to no query string).
#[derive(Serialize)]
struct NoParams {}

/// galaxyark provider, obtained via [`FinProxy::galaxyark`]. Access the three
/// sub-APIs with [`Galaxyark::ark`], [`Galaxyark::owl2`], [`Galaxyark::owl5`].
pub struct Galaxyark<'a> {
    client: &'a FinProxy,
}

impl FinProxy {
    /// Access the galaxyark (Ark / Owl2 / Owl5) endpoints.
    pub fn galaxyark(&self) -> Galaxyark<'_> {
        Galaxyark { client: self }
    }
}

impl<'a> Galaxyark<'a> {
    /// Ark sub-API (TW/US K-values, ETF NAV, reference tables).
    pub fn ark(&self) -> Ark<'a> {
        Ark { client: self.client }
    }
    /// Owl2 sub-API (legacy TW market data).
    pub fn owl2(&self) -> Owl2<'a> {
        Owl2 { client: self.client }
    }
    /// Owl5 sub-API (newer TW + US market data).
    pub fn owl5(&self) -> Owl5<'a> {
        Owl5 { client: self.client }
    }
}

/// Ark sub-client.
pub struct Ark<'a> {
    client: &'a FinProxy,
}
/// Owl2 sub-client.
pub struct Owl2<'a> {
    client: &'a FinProxy,
}
/// Owl5 sub-client.
pub struct Owl5<'a> {
    client: &'a FinProxy,
}

impl Ark<'_> {
    /// GET /v1/galaxyark/ark/stock-data
    pub async fn stock_data(&self, p: &SymbolsParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/stock-data", p).await
    }
    /// GET /v1/galaxyark/ark/stock-data-us
    pub async fn stock_data_us(&self, p: &SymbolsParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/stock-data-us", p).await
    }
    /// GET /v1/galaxyark/ark/nav-premium
    pub async fn nav_premium(&self, p: &SymbolsParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/nav-premium", p).await
    }
    /// GET /v1/galaxyark/ark/usd-twd-rate
    pub async fn usd_twd_rate(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/usd-twd-rate", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/risk
    pub async fn risk(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/risk", p).await
    }
    /// GET /v1/galaxyark/ark/all-authors
    pub async fn all_authors(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/all-authors", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/board-list
    pub async fn board_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/board-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/radio-list
    pub async fn radio_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/radio-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/ferryman-list
    pub async fn ferryman_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/ferryman-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/ferryman-line-group-list
    pub async fn ferryman_line_group_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/ferryman-line-group-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/wish-list
    pub async fn wish_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/wish-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/common-config-list
    pub async fn common_config_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/common-config-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/strategy-ids
    pub async fn strategy_ids(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/strategy-ids", &NoParams {}).await
    }
    /// GET /v1/galaxyark/ark/industry-info
    pub async fn industry_info(&self, p: &SymbolsParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/ark/industry-info", p).await
    }
}

// --- Owl2: data + broker ---
impl Owl2<'_> {
    /// GET /v1/galaxyark/owl2/intraday-kbars
    pub async fn intraday_kbars(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/intraday-kbars", p).await
    }
    /// GET /v1/galaxyark/owl2/historical-kbars
    pub async fn historical_kbars(&self, p: &KbarParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/historical-kbars", p).await
    }
    /// GET /v1/galaxyark/owl2/recent-kbars
    pub async fn recent_kbars(&self, p: &RecentKbarParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/recent-kbars", p).await
    }
    /// GET /v1/galaxyark/owl2/txf-kbars
    pub async fn txf_kbars(&self, p: &MinutesParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/txf-kbars", p).await
    }
    /// GET /v1/galaxyark/owl2/news
    pub async fn news(&self, p: &NewsParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/news", p).await
    }
    /// GET /v1/galaxyark/owl2/stock-sell-buy
    pub async fn stock_sell_buy(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/stock-sell-buy", p).await
    }
    /// GET /v1/galaxyark/owl2/money-flow
    pub async fn money_flow(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/money-flow", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/large-trading-power
    pub async fn large_trading_power(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/large-trading-power", p).await
    }
    /// GET /v1/galaxyark/owl2/sell-buy-group
    pub async fn sell_buy_group(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sell-buy-group", p).await
    }
    /// GET /v1/galaxyark/owl2/broker-top15
    pub async fn broker_top15(&self, p: &BrokerParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/broker-top15", p).await
    }
}

// --- Owl2: rankings / reference lists (no params) ---
impl Owl2<'_> {
    /// GET /v1/galaxyark/owl2/concept-long
    pub async fn concept_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/concept-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/concept-short
    pub async fn concept_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/concept-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/concept-summary
    pub async fn concept_summary(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/concept-summary", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/main-sector-long
    pub async fn main_sector_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/main-sector-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/main-sector-short
    pub async fn main_sector_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/main-sector-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/main-sector-summary
    pub async fn main_sector_summary(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/main-sector-summary", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/sector-long
    pub async fn sector_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sector-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/sector-short
    pub async fn sector_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sector-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/sector-summary
    pub async fn sector_summary(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sector-summary", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/group-shares-long
    pub async fn group_shares_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/group-shares-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/group-shares-short
    pub async fn group_shares_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/group-shares-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/group-shares-summary
    pub async fn group_shares_summary(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/group-shares-summary", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/pre-open-up
    pub async fn pre_open_up(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/pre-open-up", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/pre-open-down
    pub async fn pre_open_down(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/pre-open-down", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/pre-open-volume
    pub async fn pre_open_volume(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/pre-open-volume", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/sc-broker-ids
    pub async fn sc_broker_ids(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sc-broker-ids", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl2/sc-broker-tags
    pub async fn sc_broker_tags(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sc-broker-tags", &NoParams {}).await
    }
}

// --- Owl2: reference (parameterized) ---
impl Owl2<'_> {
    /// GET /v1/galaxyark/owl2/warrant-by-symbol
    pub async fn warrant_by_symbol(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/warrant-by-symbol", p).await
    }
    /// GET /v1/galaxyark/owl2/stock-match-details
    pub async fn stock_match_details(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/stock-match-details", p).await
    }
    /// GET /v1/galaxyark/owl2/warrant-by-id
    pub async fn warrant_by_id(&self, p: &CoidParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/warrant-by-id", p).await
    }
    /// GET /v1/galaxyark/owl2/securities-index-details
    pub async fn securities_index_details(&self, p: &IndexIdParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/securities-index-details", p).await
    }
    /// GET /v1/galaxyark/owl2/sc-concept-symbol
    pub async fn sc_concept_symbol(&self, p: &TypeNameParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sc-concept-symbol", p).await
    }
    /// GET /v1/galaxyark/owl2/sc-group-shares-symbol
    pub async fn sc_group_shares_symbol(&self, p: &TypeNameParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sc-group-shares-symbol", p).await
    }
    /// GET /v1/galaxyark/owl2/sc-economic-index
    pub async fn sc_economic_index(&self, p: &EconIndexParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/sc-economic-index", p).await
    }
    /// GET /v1/galaxyark/owl2/broker-latest-top10
    pub async fn broker_latest_top10(&self, p: &BrokerIdParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/broker-latest-top10", p).await
    }
    /// GET /v1/galaxyark/owl2/broker-days-top15
    pub async fn broker_days_top15(&self, p: &BrokerDaysParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/broker-days-top15", p).await
    }
    /// GET /v1/galaxyark/owl2/broker-daily
    pub async fn broker_daily(&self, p: &BrokerDailyParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl2/broker-daily", p).await
    }
}

// --- Owl5 (newer TW + US) ---
impl Owl5<'_> {
    /// GET /v1/galaxyark/owl5/money-flow
    pub async fn money_flow(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/money-flow", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/stock-minute-kbars
    pub async fn stock_minute_kbars(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/stock-minute-kbars", p).await
    }
    /// GET /v1/galaxyark/owl5/stock-sell-buy
    pub async fn stock_sell_buy(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/stock-sell-buy", p).await
    }
    /// GET /v1/galaxyark/owl5/us-etf-holdings
    pub async fn us_etf_holdings(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-etf-holdings", p).await
    }
    /// GET /v1/galaxyark/owl5/us-last-trades
    pub async fn us_last_trades(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-last-trades", p).await
    }
    /// GET /v1/galaxyark/owl5/us-minute-history
    pub async fn us_minute_history(&self, p: &KbarParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-minute-history", p).await
    }
    /// GET /v1/galaxyark/owl5/us-last-trading-date
    pub async fn us_last_trading_date(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-last-trading-date", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/tracking-stock
    pub async fn tracking_stock(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/tracking-stock", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/up-down-count
    pub async fn up_down_count(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/up-down-count", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/up-down-list
    pub async fn up_down_list(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/up-down-list", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/transaction-ranking
    pub async fn transaction_ranking(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/transaction-ranking", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/not-trading-days
    pub async fn not_trading_days(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/not-trading-days", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-recent-hot
    pub async fn us_recent_hot(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-recent-hot", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-etf-catalogue
    pub async fn us_etf_catalogue(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-etf-catalogue", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-hot-etf
    pub async fn us_hot_etf(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-hot-etf", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-hot-fund
    pub async fn us_hot_fund(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-hot-fund", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-news
    pub async fn us_news(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-news", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-symbol-money-flow-long
    pub async fn us_symbol_money_flow_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-symbol-money-flow-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-symbol-money-flow-short
    pub async fn us_symbol_money_flow_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-symbol-money-flow-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-stock-etf-group
    pub async fn us_stock_etf_group(&self, p: &NumbersParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-stock-etf-group", p).await
    }
    /// GET /v1/galaxyark/owl5/data-table
    pub async fn data_table(&self, p: &DataTableParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/data-table", p).await
    }
    /// GET /v1/galaxyark/owl5/us-data-table
    pub async fn us_data_table(&self, p: &DataTableParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-data-table", p).await
    }
    /// GET /v1/galaxyark/owl5/us-stock-indexes
    pub async fn us_stock_indexes(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-stock-indexes", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-sector-money-flow-long
    pub async fn us_sector_money_flow_long(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-sector-money-flow-long", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-sector-money-flow-short
    pub async fn us_sector_money_flow_short(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-sector-money-flow-short", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-date-summary
    pub async fn us_date_summary(&self) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-date-summary", &NoParams {}).await
    }
    /// GET /v1/galaxyark/owl5/us-last-sale
    pub async fn us_last_sale(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-last-sale", p).await
    }
    /// GET /v1/galaxyark/owl5/us-pre-trade-ohlc
    pub async fn us_pre_trade_ohlc(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-pre-trade-ohlc", p).await
    }
    /// GET /v1/galaxyark/owl5/us-price-volume-sum
    pub async fn us_price_volume_sum(&self, p: &SymbolParams) -> Result<Value, SdkError> {
        self.client.get("/v1/galaxyark/owl5/us-price-volume-sum", p).await
    }
}

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

    // galaxyark `data` is the raw value (here a small array).
    fn body() -> &'static str {
        r#"{"success":true,"data":[{"x":1}],"meta":{}}"#
    }

    #[tokio::test]
    async fn ark_stock_data_encodes_symbols() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/ark/stock-data"))
            .and(query_param("symbols", "2330,2317"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().ark().stock_data(&SymbolsParams::new("2330,2317")).await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn ark_usd_twd_rate_no_params() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/ark/usd-twd-rate"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().ark().usd_twd_rate().await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn ark_risk_encodes_symbol() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/ark/risk"))
            .and(query_param("symbol", "2330"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().ark().risk(&SymbolParams::new("2330")).await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl2_historical_kbars_encodes_symbol_and_minutes() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl2/historical-kbars"))
            .and(query_param("symbol", "2330"))
            .and(query_param("minutes", "5"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client
            .galaxyark()
            .owl2()
            .historical_kbars(&KbarParams::new("2330").minutes(5))
            .await
            .unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl2_broker_top15_encodes_required_dates() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl2/broker-top15"))
            .and(query_param("symbol", "2330"))
            .and(query_param("start", "2024-01-02"))
            .and(query_param("end", "2024-01-31"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client
            .galaxyark()
            .owl2()
            .broker_top15(&BrokerParams::new("2330", "2024-01-02", "2024-01-31"))
            .await
            .unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl2_concept_long_no_params() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl2/concept-long"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().owl2().concept_long().await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl2_warrant_by_id_encodes_coid() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl2/warrant-by-id"))
            .and(query_param("coid", "030001"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().owl2().warrant_by_id(&CoidParams::new("030001")).await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl2_broker_daily_encodes_symbol_and_broker() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl2/broker-daily"))
            .and(query_param("symbol", "2330"))
            .and(query_param("broker_id", "9200"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client
            .galaxyark()
            .owl2()
            .broker_daily(&BrokerDailyParams::new("2330", "9200"))
            .await
            .unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl5_us_last_sale_encodes_symbol() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl5/us-last-sale"))
            .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 v = client.galaxyark().owl5().us_last_sale(&SymbolParams::new("AAPL")).await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl5_money_flow_no_params() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl5/money-flow"))
            .respond_with(ResponseTemplate::new(200).set_body_string(body()))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client.galaxyark().owl5().money_flow().await.unwrap();
        assert!(v.is_array());
    }

    #[tokio::test]
    async fn owl5_data_table_encodes_params() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl5/data-table"))
            .and(query_param("table_id", "BU1-10001a"))
            .and(query_param("symbol", "2330"))
            .and(query_param("numbers", "200"))
            .respond_with(ResponseTemplate::new(200).set_body_string(
                r#"{"success":true,"data":{"Title":["a"],"Data":[]},"meta":{}}"#,
            ))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client
            .galaxyark()
            .owl5()
            .data_table(&DataTableParams::new("BU1-10001a", "2330", 200))
            .await
            .unwrap();
        assert!(v.is_object());
    }

    #[tokio::test]
    async fn owl5_us_data_table_encodes_params() {
        let server = MockServer::start().await;
        Mock::given(method("GET"))
            .and(path("/v1/galaxyark/owl5/us-data-table"))
            .and(query_param("table_id", "BU5-1a"))
            .and(query_param("symbol", "AAPL"))
            .and(query_param("numbers", "50"))
            .respond_with(ResponseTemplate::new(200).set_body_string(
                r#"{"success":true,"data":{"Title":["a"],"Data":[]},"meta":{}}"#,
            ))
            .mount(&server)
            .await;
        let client = FinProxy::new(server.uri()).unwrap();
        let v = client
            .galaxyark()
            .owl5()
            .us_data_table(&DataTableParams::new("BU5-1a", "AAPL", 50))
            .await
            .unwrap();
        assert!(v.is_object());
    }
}
