use chrono::NaiveDateTime;

/// NEM region identifier.
///
/// The five regions of the Australian National Electricity Market:
///
/// | Variant | State |
/// |---------|-------|
/// | `NSW1` | New South Wales |
/// | `QLD1` | Queensland |
/// | `SA1` | South Australia |
/// | `TAS1` | Tasmania |
/// | `VIC1` | Victoria |
///
/// # Examples
///
/// ```
/// use nemweb::Region;
///
/// let region = Region::NSW1;
/// assert_eq!(region.as_str(), "NSW1");
/// assert_eq!(region.to_string(), "NSW1");
///
/// // Implements std::str::FromStr
/// let parsed: Region = "QLD1".parse().unwrap();
/// assert_eq!(parsed, Region::QLD1);
/// assert!("INVALID".parse::<Region>().is_err());
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum Region {
    /// New South Wales
    NSW1,
    /// Queensland
    QLD1,
    /// South Australia
    SA1,
    /// Tasmania
    TAS1,
    /// Victoria
    VIC1,
}

impl Region {
    /// Returns the string representation (e.g. `"NSW1"`).
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::NSW1 => "NSW1",
            Self::QLD1 => "QLD1",
            Self::SA1 => "SA1",
            Self::TAS1 => "TAS1",
            Self::VIC1 => "VIC1",
        }
    }

}

impl std::fmt::Display for Region {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_str(self.as_str())
    }
}

impl std::str::FromStr for Region {
    type Err = ParseRegionError;

    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "NSW1" => Ok(Self::NSW1),
            "QLD1" => Ok(Self::QLD1),
            "SA1" => Ok(Self::SA1),
            "TAS1" => Ok(Self::TAS1),
            "VIC1" => Ok(Self::VIC1),
            _ => Err(ParseRegionError(s.to_string())),
        }
    }
}

/// Error returned when parsing an invalid region string.
#[derive(Debug, Clone)]
pub struct ParseRegionError(pub String);

impl std::fmt::Display for ParseRegionError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "unknown NEM region: {:?}", self.0)
    }
}

impl std::error::Error for ParseRegionError {}

/// 5-minute dispatch interval price.
///
/// Each NEM dispatch interval is 5 minutes. AEMO publishes one price
/// per region per interval based on the dispatch engine's solution.
///
/// # Fields
///
/// - `region` — the NEM region this price applies to.
/// - `settlement_date` — interval ending timestamp (AEST, not UTC).
/// - `price` — Regional Reference Price in $/MWh. Range: −$1,000 to $17,500.
/// - `apc_flag` — Administered Price Cap bitmask. `0` = no cap applied;
///   non-zero means one or more prices were capped by AEMO. Bit 0 is
///   the energy price, the remaining bits are individual FCAS prices.
///   Use `apc_flag & 1 != 0` to detect energy price capping.
/// - `market_suspended_flag` — market suspension state. `0` = normal
///   market operation; non-zero means the market was in an administered
///   or suspended state and `price` is **not** a normal market-clearing
///   price. Most notably non-zero during the June 2022 NEM suspension.
///
/// # Historical caveat
///
/// When doing historical analysis, always check these flags. Intervals
/// with non-zero `market_suspended_flag` carry Administered Prices
/// (typically ≤ $300/MWh) instead of real market-clearing prices, so
/// mixing them into averages or summaries will distort results.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// let prices = nemweb::dispatch().fetch().await?;
/// for p in &prices {
///     println!("{} ${:.2}/MWh at {}", p.region, p.price, p.settlement_date);
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct DispatchPrice {
    /// NEM region this price applies to.
    pub region: Region,
    /// Interval-ending timestamp (AEST, not UTC).
    pub settlement_date: NaiveDateTime,
    /// Regional Reference Price ($/MWh).
    pub price: f64,
    /// Administered Price Cap bitmask. `0` = no APC applied. Bit 0 is
    /// the energy price, remaining bits are individual FCAS prices.
    pub apc_flag: u8,
    /// Market suspension state. `0` = normal; non-zero = market in an
    /// administered / suspended state. See type-level docs.
    pub market_suspended_flag: u8,
}

/// Trading interval (settlement) price.
///
/// Since the AEMO 5-Minute Settlement reform (5MS) on 1 October 2021,
/// trading intervals are 5 minutes long — the same cadence as dispatch —
/// and the trading price equals the dispatch price for each interval. The
/// [`TradingPrice`] and [`DispatchPrice`] records are populated from
/// different AEMO tables (`TRADINGPRICE` vs `DISPATCHPRICE`), but carry
/// the same RRP values post-5MS.
///
/// Before 5MS, the trading price was the 30-minute time-weighted average
/// of six 5-minute dispatch prices. Historical queries over pre-5MS
/// periods will return records at the original 30-minute cadence.
///
/// # Fields
///
/// - `region` — the NEM region.
/// - `settlement_date` — trading interval ending timestamp (AEST).
/// - `price` — Regional Reference Price in $/MWh.
/// - `invalid` — `true` if AEMO flagged this row as invalid
///   (`INVALIDFLAG=1`). Invalid rows may contain stale or placeholder
///   values; filter them out for clean analysis.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// let prices = nemweb::trading().fetch().await?;
/// for p in &prices {
///     println!("{} ${:.2}/MWh at {}", p.region, p.price, p.settlement_date);
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct TradingPrice {
    /// NEM region this price applies to.
    pub region: Region,
    /// Trading interval ending timestamp (AEST).
    pub settlement_date: NaiveDateTime,
    /// Regional Reference Price ($/MWh).
    pub price: f64,
    /// AEMO-flagged invalid row (`INVALIDFLAG=1`). Filter these out
    /// for clean analysis.
    pub invalid: bool,
}

/// Region demand and generation summary per 5-minute dispatch interval.
///
/// Provides the aggregate demand, available generation capacity, and
/// net interchange across the region boundary for each NEM region.
///
/// # Fields
///
/// - `settlement_date` — dispatch interval ending timestamp (AEST, not UTC).
/// - `total_demand` — total region demand in MW.
/// - `available_generation` — total available generation capacity in MW.
/// - `net_interchange` — net flow across the region boundary in MW.
///   **Positive = net export** (power leaving the region);
///   **negative = net import**. This matches AEMO's `NETINTERCHANGE`
///   convention in the `DISPATCHREGIONSUM` table.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// let demand = nemweb::demand().fetch().await?;
/// for d in &demand {
///     println!(
///         "{}: demand={:.0}MW, gen={:.0}MW, net_ix={:.0}MW",
///         d.region, d.total_demand, d.available_generation, d.net_interchange
///     );
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RegionDemand {
    /// NEM region.
    pub region: Region,
    /// Dispatch interval ending timestamp (AEST).
    pub settlement_date: NaiveDateTime,
    /// Total demand (MW).
    pub total_demand: f64,
    /// Available generation capacity (MW).
    pub available_generation: f64,
    /// Net interchange across the region boundary (MW).
    /// Positive = net export; negative = net import.
    pub net_interchange: f64,
}

/// Interconnector flow per 5-minute dispatch interval.
///
/// NEM interconnectors link adjacent regions. Flow direction is
/// defined as region 1 → region 2 (positive MW = that direction).
///
/// Common interconnector IDs: `NSW1-QLD1`, `VIC1-NSW1`, `V-SA`,
/// `V-S-MNSP1`, `T-V-MNSP1`, `N-Q-MNSP1`.
///
/// All timestamps are AEST (Australian Eastern Standard Time), not UTC.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// // All interconnectors
/// let flows = nemweb::interconnector().fetch().await?;
///
/// // Single interconnector
/// let vic_nsw = nemweb::interconnector()
///     .id("VIC1-NSW1")
///     .fetch().await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct InterconnectorFlow {
    /// AEMO interconnector identifier (e.g. `"VIC1-NSW1"`, `"T-V-MNSP1"`).
    pub interconnector_id: String,
    /// Dispatch interval ending timestamp (AEST).
    pub settlement_date: NaiveDateTime,
    /// Target MW flow from the NEMDE dispatch solution. This is what the
    /// market engine *instructed* the interconnector to flow. For historical
    /// economic analysis this is usually the right value. Positive = flow
    /// from the "from" region to the "to" region per the interconnector ID.
    pub mw_flow: f64,
    /// Actual metered MW flow measured by SCADA. May differ from `mw_flow`
    /// by tens of MW (average) or up to ~1500 MW during constraint or
    /// response events. For physical/engineering analysis of what
    /// actually flowed across the boundary, prefer this value.
    pub metered_mw_flow: f64,
    /// MW losses on the interconnector.
    pub mw_losses: f64,
}

/// Pre-dispatch price forecast (30-minute intervals, up to ~40 hours ahead).
///
/// AEMO publishes rolling forecasts of future dispatch prices. These are
/// indicative only and can change significantly before the actual interval.
///
/// All timestamps are AEST (Australian Eastern Standard Time), not UTC.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// let forecasts = nemweb::predispatch().fetch().await?;
/// for f in forecasts.iter().take(5) {
///     println!("{} ${:.2}/MWh at {}", f.region, f.price, f.forecast_time);
/// }
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PredispatchPrice {
    /// NEM region the forecast applies to.
    pub region: Region,
    /// Forecast target time (interval ending, AEST).
    pub forecast_time: NaiveDateTime,
    /// Forecast regional reference price ($/MWh).
    pub price: f64,
}

/// Unit SCADA telemetry — actual MW output per 5-minute interval.
///
/// Reports the real-time SCADA reading for each dispatchable unit
/// (generator, load, or battery) in the NEM.
///
/// The `duid` (Dispatchable Unit Identifier) uniquely identifies each
/// unit — e.g. `"BASTYAN"` (Bastyan power station, Tasmania).
///
/// All timestamps are AEST (Australian Eastern Standard Time), not UTC.
///
/// # Examples
///
/// ```no_run
/// # async fn example() -> nemweb::Result<()> {
/// // All units
/// let scada = nemweb::scada().fetch().await?;
///
/// // Single unit
/// let unit = nemweb::scada().duid("BASTYAN").fetch().await?;
/// # Ok(())
/// # }
/// ```
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct UnitScada {
    /// Dispatchable Unit Identifier (e.g. `"BASTYAN"`).
    pub duid: String,
    /// Dispatch interval ending timestamp (AEST).
    pub settlement_date: NaiveDateTime,
    /// SCADA MW reading. May be negative for loads and charging batteries.
    pub scada_value: f64,
}
