use std::path::Path;
use std::sync::Arc;

use serde_json::{Map, Value};

use crate::browser::BrowserBackend;
use crate::compat::direct_skill_runtime::DirectSubmitOutcome;
use crate::config::SgClawSettings;
use crate::pipe::{BrowserPipeTool, PipeError, Transport};

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DeterministicExecutionPlan {
    pub instruction: String,
    pub tool_name: String,
    pub expected_domain: String,
    pub org_label: String,
    pub org_code: String,
    pub period_mode: String,
    pub period_mode_code: String,
    pub period_value: String,
    pub period_payload: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeterministicSubmitDecision {
    NotDeterministic,
    Prompt { summary: String },
    Execute(DeterministicExecutionPlan),
}

const DETERMINISTIC_SUFFIX: &str = "。。。";
const LINELLOSS_HOST: &str = "20.76.57.61";
const LINELLOSS_TOOL: &str = "tq-lineloss-report.collect_lineloss";

pub fn decide_deterministic_submit(
    raw_instruction: &str,
    page_url: Option<&str>,
    page_title: Option<&str>,
) -> DeterministicSubmitDecision {
    let Some(instruction) = strip_exact_deterministic_suffix(raw_instruction) else {
        return DeterministicSubmitDecision::NotDeterministic;
    };

    let normalized_instruction = instruction.trim();
    if normalized_instruction.is_empty() {
        return unsupported_scene_prompt();
    }

    if !matches_lineloss_scene(normalized_instruction) {
        return unsupported_scene_prompt();
    }

    let resolved_org = match crate::compat::tq_lineloss::org_resolver::resolve_org_from_instruction(
        normalized_instruction,
    ) {
        Ok(Some(resolved_org)) => resolved_org,
        Ok(None) => {
            return DeterministicSubmitDecision::Prompt {
                summary: crate::compat::tq_lineloss::contracts::missing_company_prompt(),
            };
        }
        Err(summary) => {
            return DeterministicSubmitDecision::Prompt { summary };
        }
    };

    let resolved_period = match crate::compat::tq_lineloss::period_resolver::resolve_period(
        normalized_instruction,
    ) {
        Ok(resolved_period) => resolved_period,
        Err(summary) => {
            return DeterministicSubmitDecision::Prompt { summary };
        }
    };

    if page_context_conflicts_with_lineloss(page_url, page_title) {
        return DeterministicSubmitDecision::Prompt {
            summary:
                "已命中台区线损报表技能，但当前页面与台区线损场景不匹配，请切换到线损页面后重试。"
                    .to_string(),
        };
    }

    DeterministicSubmitDecision::Execute(DeterministicExecutionPlan {
        instruction: normalized_instruction.to_string(),
        tool_name: LINELLOSS_TOOL.to_string(),
        expected_domain: LINELLOSS_HOST.to_string(),
        org_label: resolved_org.label,
        org_code: resolved_org.code,
        period_mode: period_mode_name(&resolved_period.mode).to_string(),
        period_mode_code: resolved_period.mode_code,
        period_value: resolved_period.value,
        period_payload: serde_json::to_string(&resolved_period.payload)
            .unwrap_or_else(|_| "{}".to_string()),
    })
}

pub fn execute_deterministic_submit<T: Transport + 'static>(
    browser_tool: BrowserPipeTool<T>,
    plan: &DeterministicExecutionPlan,
    workspace_root: &Path,
    settings: &SgClawSettings,
) -> Result<DirectSubmitOutcome, PipeError> {
    let args = deterministic_submit_args(plan);
    let output = crate::compat::direct_skill_runtime::execute_browser_script_skill_raw_output(
        browser_tool,
        &plan.tool_name,
        workspace_root,
        settings,
        args,
    )?;

    Ok(summarize_lineloss_output(&output))
}

pub fn execute_deterministic_submit_with_browser_backend(
    browser_backend: Arc<dyn BrowserBackend>,
    plan: &DeterministicExecutionPlan,
    workspace_root: &Path,
    settings: &SgClawSettings,
) -> Result<DirectSubmitOutcome, PipeError> {
    let args = deterministic_submit_args(plan);
    let output =
        crate::compat::direct_skill_runtime::execute_browser_script_skill_raw_output_with_browser_backend(
            browser_backend,
            &plan.tool_name,
            workspace_root,
            settings,
            args,
        )?;

    Ok(summarize_lineloss_output(&output))
}

fn deterministic_submit_args(plan: &DeterministicExecutionPlan) -> Map<String, Value> {
    let mut args = Map::new();
    args.insert(
        "expected_domain".to_string(),
        Value::String(plan.expected_domain.clone()),
    );
    args.insert(
        "org_label".to_string(),
        Value::String(plan.org_label.clone()),
    );
    args.insert(
        "org_code".to_string(),
        Value::String(plan.org_code.clone()),
    );
    args.insert(
        "period_mode".to_string(),
        Value::String(plan.period_mode.clone()),
    );
    args.insert(
        "period_mode_code".to_string(),
        Value::String(plan.period_mode_code.clone()),
    );
    args.insert(
        "period_value".to_string(),
        Value::String(plan.period_value.clone()),
    );
    args.insert(
        "period_payload".to_string(),
        Value::String(plan.period_payload.clone()),
    );
    args
}

fn summarize_lineloss_output(output: &str) -> DirectSubmitOutcome {
    let Some(payload) = serde_json::from_str::<Value>(output).ok() else {
        return DirectSubmitOutcome {
            success: true,
            summary: output.to_string(),
        };
    };

    let artifact = payload
        .as_object()
        .and_then(|object| object.get("text"))
        .unwrap_or(&payload);

    summarize_lineloss_artifact(artifact)
}

fn summarize_lineloss_artifact(artifact: &Value) -> DirectSubmitOutcome {
    let Some(artifact) = artifact.as_object() else {
        return DirectSubmitOutcome {
            success: true,
            summary: artifact.to_string(),
        };
    };

    if artifact.get("type").and_then(Value::as_str) != Some("report-artifact") {
        return DirectSubmitOutcome {
            success: true,
            summary: Value::Object(artifact.clone()).to_string(),
        };
    }

    let status = artifact
        .get("status")
        .and_then(Value::as_str)
        .unwrap_or("ok");
    let success = matches!(status, "ok" | "partial" | "empty");
    let report_name = artifact
        .get("report_name")
        .and_then(Value::as_str)
        .unwrap_or("tq-lineloss-report");
    let org_label = artifact
        .get("org")
        .and_then(Value::as_object)
        .and_then(|org| org.get("label"))
        .and_then(Value::as_str)
        .unwrap_or("");
    let period_value = artifact
        .get("period")
        .and_then(Value::as_object)
        .and_then(|period| period.get("value"))
        .and_then(Value::as_str)
        .unwrap_or("");
    let rows = artifact
        .get("counts")
        .and_then(Value::as_object)
        .and_then(|counts| counts.get("rows"))
        .and_then(Value::as_u64)
        .map(|value| value as usize)
        .or_else(|| artifact.get("rows").and_then(Value::as_array).map(Vec::len))
        .unwrap_or(0);
    let reasons = artifact
        .get("reasons")
        .and_then(Value::as_array)
        .map(|reasons| {
            reasons
                .iter()
                .filter_map(Value::as_str)
                .filter(|value| !value.trim().is_empty())
                .collect::<Vec<_>>()
        })
        .unwrap_or_default();

    let mut parts = vec![report_name.to_string()];
    if !org_label.is_empty() {
        parts.push(org_label.to_string());
    }
    if !period_value.is_empty() {
        parts.push(period_value.to_string());
    }
    parts.push(format!("status={status}"));
    parts.push(format!("rows={rows}"));
    if !reasons.is_empty() {
        parts.push(format!("reasons={}", reasons.join(",")));
    }

    DirectSubmitOutcome {
        success,
        summary: parts.join(" "),
    }
}

fn strip_exact_deterministic_suffix(raw_instruction: &str) -> Option<&str> {
    let without_suffix = raw_instruction.strip_suffix(DETERMINISTIC_SUFFIX)?;
    if without_suffix.ends_with('。') {
        return None;
    }
    Some(without_suffix)
}

fn matches_lineloss_scene(instruction: &str) -> bool {
    instruction.contains("线损") || instruction.contains("月累计") || instruction.contains("周累计")
}

fn page_context_conflicts_with_lineloss(page_url: Option<&str>, page_title: Option<&str>) -> bool {
    let url = page_url.unwrap_or_default().to_ascii_lowercase();
    let title = page_title.unwrap_or_default();
    let has_context = !url.is_empty() || !title.is_empty();
    if !has_context {
        return false;
    }

    let url_matches = url.contains(LINELLOSS_HOST) || url.contains("lineloss");
    let title_matches = title.contains("线损");

    !(url_matches || title_matches)
}

fn period_mode_name(mode: &crate::compat::tq_lineloss::contracts::PeriodMode) -> &'static str {
    match mode {
        crate::compat::tq_lineloss::contracts::PeriodMode::Month => "month",
        crate::compat::tq_lineloss::contracts::PeriodMode::Week => "week",
    }
}

fn unsupported_scene_prompt() -> DeterministicSubmitDecision {
    DeterministicSubmitDecision::Prompt {
        summary: "确定性提交当前只支持台区线损月/周累计线损率报表场景，请补充台区线损请求。"
            .to_string(),
    }
}
