//! MCP tool surface for the SCADA platform.
//!
//! Reads (telemetry, history, trends, alarms, outage queue, work orders) are
//! `read_only`. Telemetry ingest and alarm ack/clear are `internal_write`.
//!
//! SAFETY-CRITICAL writes are `external_write` + gated (`requires_approval`):
//! `issue_command` (acts on physical points — interlock-validated), `set_lockout`,
//! and `set_asset_state`. `check_interlocks` is a read-only dry-run agents should
//! call before requesting an approval-gated command.

use crate::store::ScadaStore;
use crate::types::*;
use adk_mcp_sdk::{HealthCheck, HealthStatus};
use chrono::NaiveDate;
use rmcp::{handler::server::wrapper::Parameters, schemars, tool, tool_router};
use serde::Deserialize;
use std::sync::Arc;

fn dactor() -> String { "agent".into() }
fn dgood() -> String { "good".into() }
fn date(s: &Option<String>) -> Option<NaiveDate> { s.as_ref().and_then(|x| NaiveDate::parse_from_str(x, "%Y-%m-%d").ok()) }
fn dlimit() -> usize { 50 }

// ─── inputs ───────────────────────────────────────────────────────────────

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CreateSiteInput { pub name: String, pub sector: Sector, #[serde(default)] pub region: String, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ListSitesInput { pub sector: Option<Sector> }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CreateAssetInput { pub site_id: String, pub name: String, #[serde(default = "dkind")] pub kind: String, #[serde(default = "dactor")] pub actor: String }
fn dkind() -> String { "asset".into() }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct AssetIdInput { pub asset_id: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ListAssetsInput { pub site_id: Option<String>, pub kind: Option<String>, pub state: Option<AssetState> }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct SetAssetStateInput { pub asset_id: String, pub state: AssetState, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct SetLockoutInput { pub asset_id: String, pub lockout: bool, #[serde(default = "dactor")] pub actor: String }

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CreateTagInput {
    pub asset_id: String,
    pub name: String,
    pub point_type: PointType,
    #[serde(default)] pub unit: String,
    pub min: Option<f64>,
    pub max: Option<f64>,
    pub warn_high: Option<f64>,
    pub crit_high: Option<f64>,
    pub warn_low: Option<f64>,
    pub crit_low: Option<f64>,
    #[serde(default)] pub value: f64,
    #[serde(default = "dactor")] pub actor: String,
}
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct TagIdInput { pub tag_id: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ListTagsInput { pub asset_id: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct IngestReadingInput { pub tag_id: String, pub value: f64, #[serde(default = "dgood")] pub quality: String, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct TagHistoryInput { pub tag_id: String, #[serde(default = "dlimit")] pub limit: usize }

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ListAlarmsInput { #[serde(default = "dtrue")] pub active_only: bool, pub severity: Option<AlarmSeverity> }
fn dtrue() -> bool { true }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct RaiseAlarmInput { pub asset_id: String, pub severity: AlarmSeverity, pub message: String, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct AlarmIdInput { pub alarm_id: String, #[serde(default = "dactor")] pub actor: String }

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct IssueCommandInput { pub tag_id: String, #[serde(default = "dset")] pub action: String, pub value: f64, #[serde(default = "dactor")] pub issued_by: String }
fn dset() -> String { "set".into() }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CheckInterlocksInput { pub tag_id: String, pub value: f64 }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CommandHistoryInput { pub asset_id: Option<String>, #[serde(default = "dlimit")] pub limit: usize }

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ReportOutageInput { pub site_id: String, pub asset_id: Option<String>, #[serde(default)] pub cause: String, #[serde(default)] pub customers_affected: u32, #[serde(default)] pub safety_hazard: bool, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct UpdateOutageInput { pub outage_id: String, pub status: OutageStatus, #[serde(default = "dactor")] pub actor: String }

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct CreateWorkOrderInput { pub asset_id: String, #[serde(default = "dpre")] pub kind: String, #[serde(default = "dmed")] pub priority: String, #[serde(default)] pub description: String, pub scheduled_for: Option<String>, #[serde(default = "dactor")] pub actor: String }
fn dpre() -> String { "preventive".into() }
fn dmed() -> String { "medium".into() }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct UpdateWorkOrderInput { pub wo_id: String, pub status: Option<WorkOrderStatus>, pub assigned_to: Option<String>, pub scheduled_for: Option<String>, #[serde(default = "dactor")] pub actor: String }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct ListWorkOrdersInput { pub asset_id: Option<String>, pub status: Option<WorkOrderStatus> }
#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct AuditLogInput { #[serde(default = "dlimit")] pub limit: usize }

// ─── server ───────────────────────────────────────────────────────────────

#[derive(Clone)]
pub struct ScadaServer { pub store: Arc<ScadaStore> }

#[tool_router(server_handler)]
impl ScadaServer {
    // sites & assets
    #[tool(description = "Register a site (substation, regulator station, pump station, solar array). Sector: electric/gas/water/renewable.")]
    fn create_site(&self, Parameters(i): Parameters<CreateSiteInput>) -> String {
        let s = self.store.create_site(&i.name, i.sector, &i.region, &i.actor);
        serde_json::to_string_pretty(&s).unwrap()
    }

    #[tool(description = "List sites, optionally by sector.")]
    fn list_sites(&self, Parameters(i): Parameters<ListSitesInput>) -> String {
        let v = self.store.list_sites(i.sector);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "sites": v})).unwrap()
    }

    #[tool(description = "Register an asset at a site (breaker, feeder, regulator, valve, pump, inverter, ...).")]
    fn create_asset(&self, Parameters(i): Parameters<CreateAssetInput>) -> String {
        match self.store.create_asset(&i.site_id, &i.name, &i.kind, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Get an asset by id (state, lockout).")]
    fn get_asset(&self, Parameters(i): Parameters<AssetIdInput>) -> String {
        match self.store.get_asset(&i.asset_id) {
            Some(a) => serde_json::to_string_pretty(&a).unwrap(), None => format!("Asset not found: {}", i.asset_id) }
    }

    #[tool(description = "List assets, optionally by site, kind, and/or state.")]
    fn list_assets(&self, Parameters(i): Parameters<ListAssetsInput>) -> String {
        let v = self.store.list_assets(i.site_id.as_deref(), i.kind.as_deref(), i.state);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "assets": v})).unwrap()
    }

    #[tool(description = "Set an asset's operating state (in_service/out_of_service/locked_out/faulted). Affects control interlocks — gated.")]
    fn set_asset_state(&self, Parameters(i): Parameters<SetAssetStateInput>) -> String {
        match self.store.set_asset_state(&i.asset_id, i.state, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Apply or release a lockout/tagout (LOTO) hold. While locked, all control commands on the asset are blocked. Safety control — gated.")]
    fn set_lockout(&self, Parameters(i): Parameters<SetLockoutInput>) -> String {
        match self.store.set_lockout(&i.asset_id, i.lockout, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    // tags / points
    #[tool(description = "Define a tag/point on an asset. point_type: analog/status (read-only) or setpoint/control (writable). Provide ranges and warn/crit thresholds for alarms.")]
    fn create_tag(&self, Parameters(i): Parameters<CreateTagInput>) -> String {
        match self.store.create_tag(&i.asset_id, &i.name, i.point_type, &i.unit, i.min, i.max, i.warn_high, i.crit_high, i.warn_low, i.crit_low, i.value, &i.actor) {
            Ok(t) => serde_json::to_string_pretty(&t).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Get a tag's current value, quality, ranges, and thresholds.")]
    fn get_tag(&self, Parameters(i): Parameters<TagIdInput>) -> String {
        match self.store.get_tag(&i.tag_id) {
            Some(t) => serde_json::to_string_pretty(&t).unwrap(), None => format!("Tag not found: {}", i.tag_id) }
    }

    #[tool(description = "List an asset's tags/points with live values.")]
    fn list_tags(&self, Parameters(i): Parameters<ListTagsInput>) -> String {
        let v = self.store.list_tags(&i.asset_id);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "tags": v})).unwrap()
    }

    #[tool(description = "Ingest a telemetry reading for a point (field/simulator update). Records history and auto-raises/clears threshold alarms. Not a control action.")]
    fn ingest_reading(&self, Parameters(i): Parameters<IngestReadingInput>) -> String {
        match self.store.ingest_reading(&i.tag_id, i.value, &i.quality, &i.actor) {
            Ok(t) => serde_json::to_string_pretty(&t).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Historian: recent readings for a tag, newest first.")]
    fn tag_history(&self, Parameters(i): Parameters<TagHistoryInput>) -> String {
        let v = self.store.tag_history(&i.tag_id, i.limit);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "readings": v})).unwrap()
    }

    #[tool(description = "Trend summary for a tag: samples, current, min/max/mean, and rising/falling/flat direction.")]
    fn tag_trend(&self, Parameters(i): Parameters<TagIdInput>) -> String {
        match self.store.tag_trend(&i.tag_id) {
            Some(v) => serde_json::to_string_pretty(&v).unwrap(), None => format!("Tag not found: {}", i.tag_id) }
    }

    // alarms
    #[tool(description = "List alarms (active_only defaults true), optionally by severity.")]
    fn list_alarms(&self, Parameters(i): Parameters<ListAlarmsInput>) -> String {
        let v = self.store.list_alarms(i.active_only, i.severity);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "alarms": v})).unwrap()
    }

    #[tool(description = "Manually raise an alarm on an asset (info/warning/critical).")]
    fn raise_alarm(&self, Parameters(i): Parameters<RaiseAlarmInput>) -> String {
        match self.store.raise_alarm(&i.asset_id, i.severity, &i.message, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Acknowledge an active alarm.")]
    fn acknowledge_alarm(&self, Parameters(i): Parameters<AlarmIdInput>) -> String {
        match self.store.acknowledge_alarm(&i.alarm_id, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Clear an alarm (condition resolved).")]
    fn clear_alarm(&self, Parameters(i): Parameters<AlarmIdInput>) -> String {
        match self.store.clear_alarm(&i.alarm_id, &i.actor) {
            Ok(a) => serde_json::to_string_pretty(&a).unwrap(), Err(e) => format!("Error: {e}") }
    }

    // control (safety-critical)
    #[tool(description = "DRY-RUN: evaluate all safety interlocks for a prospective command WITHOUT applying it. Read-only. Agents should call this before requesting a gated issue_command.")]
    fn check_interlocks(&self, Parameters(i): Parameters<CheckInterlocksInput>) -> String {
        match self.store.check_interlocks(&i.tag_id, i.value) {
            Ok(v) => serde_json::to_string_pretty(&v).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "SAFETY-CRITICAL: issue a control command to a writable point (setpoint value, or 0/1 for open/close/start/stop). Runs the interlock chain; only an all-pass command is applied. Rejected commands are recorded, never executed. External write — gated.")]
    fn issue_command(&self, Parameters(i): Parameters<IssueCommandInput>) -> String {
        match self.store.issue_command(&i.tag_id, &i.action, i.value, &i.issued_by) {
            Ok(c) => serde_json::to_string_pretty(&c).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Control command history (executed + rejected), optionally for one asset.")]
    fn command_history(&self, Parameters(i): Parameters<CommandHistoryInput>) -> String {
        let v = self.store.command_history(i.asset_id.as_deref(), i.limit);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "commands": v})).unwrap()
    }

    // outages
    #[tool(description = "Report an outage; auto-computes triage priority (P1 highest) from safety hazard + customers affected. Powers the Grid Outage Triage agent.")]
    fn report_outage(&self, Parameters(i): Parameters<ReportOutageInput>) -> String {
        match self.store.report_outage(&i.site_id, i.asset_id, &i.cause, i.customers_affected, i.safety_hazard, &i.actor) {
            Ok(o) => serde_json::to_string_pretty(&o).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Update an outage's status (reported/triaged/crew_dispatched/restored).")]
    fn update_outage_status(&self, Parameters(i): Parameters<UpdateOutageInput>) -> String {
        match self.store.update_outage_status(&i.outage_id, i.status, &i.actor) {
            Ok(o) => serde_json::to_string_pretty(&o).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Outage triage queue: active outages, highest priority first.")]
    fn outage_queue(&self, Parameters(_): Parameters<EmptyInput>) -> String {
        let v = self.store.outage_queue();
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "outages": v})).unwrap()
    }

    // maintenance
    #[tool(description = "Create a maintenance work order (preventive/corrective/inspection/emergency) on an asset.")]
    fn create_work_order(&self, Parameters(i): Parameters<CreateWorkOrderInput>) -> String {
        match self.store.create_work_order(&i.asset_id, &i.kind, &i.priority, &i.description, date(&i.scheduled_for), &i.actor) {
            Ok(w) => serde_json::to_string_pretty(&w).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "Update a work order (status, assignee, schedule).")]
    fn update_work_order(&self, Parameters(i): Parameters<UpdateWorkOrderInput>) -> String {
        match self.store.update_work_order(&i.wo_id, i.status, i.assigned_to, date(&i.scheduled_for), &i.actor) {
            Ok(w) => serde_json::to_string_pretty(&w).unwrap(), Err(e) => format!("Error: {e}") }
    }

    #[tool(description = "List work orders, optionally by asset and/or status.")]
    fn list_work_orders(&self, Parameters(i): Parameters<ListWorkOrdersInput>) -> String {
        let v = self.store.list_work_orders(i.asset_id.as_deref(), i.status);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "work_orders": v})).unwrap()
    }

    #[tool(description = "Recent command/operations audit-trail entries (most recent first).")]
    fn audit_log(&self, Parameters(i): Parameters<AuditLogInput>) -> String {
        let v = self.store.audit_log(i.limit);
        serde_json::to_string_pretty(&serde_json::json!({"count": v.len(), "entries": v})).unwrap()
    }
}

#[derive(Debug, Deserialize, schemars::JsonSchema)]
pub struct EmptyInput {}

#[async_trait::async_trait]
impl HealthCheck for ScadaServer {
    async fn check_health(&self) -> HealthStatus {
        HealthStatus { healthy: true, message: Some("operational".into()), latency_ms: Some(1) }
    }
}
