use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use thiserror::Error;

use crate::pipe::{Action, AgentMessage, BrowserPipeTool, Transport};

const ZHIHU_ARTICLE_URL_PREFIX: &str = "https://zhuanlan.zhihu.com/p/";
const ZHIHU_ARTICLE_EDIT_SUFFIX: &str = "/edit";

fn default_publish() -> bool {
    true
}

#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct ZhihuWriteRequest {
    pub title: String,
    pub body: String,
    #[serde(default = "default_publish")]
    pub publish: bool,
}

#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct ZhihuWriteResult {
    pub summary: String,
    pub published: bool,
    pub final_url: Option<String>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct ZhihuFlow {
    pub entry_url: String,
    pub editor_url: String,
    pub domains: HashMap<String, String>,
    pub literals: HashMap<String, String>,
    pub selectors: HashMap<String, String>,
    pub steps: Vec<FlowStep>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct FlowStep {
    pub name: String,
    pub action: String,
    pub expected_domain: String,
    pub selector_ref: Option<String>,
    pub url_ref: Option<String>,
    pub text_source: Option<String>,
    #[serde(default)]
    pub clear_first: bool,
    pub wait_after_ms: Option<u64>,
    pub timeout_ms: Option<u64>,
    pub outer: Option<bool>,
    pub x: Option<i64>,
    pub y: Option<i64>,
    #[serde(default)]
    pub only_when_publish: bool,
    pub expect_contains: Option<String>,
    pub expect_text_source: Option<String>,
    #[serde(default)]
    pub allow_empty_text: bool,
    #[serde(default)]
    pub capture_url: bool,
    pub log_message: String,
}

#[derive(Debug, Error)]
pub enum ZhihuSkillError {
    #[error("title 不能为空")]
    EmptyTitle,
    #[error("body 不能为空")]
    EmptyBody,
    #[error("failed to load zhihu flow: {0}")]
    FlowLoad(String),
    #[error("unknown action in zhihu flow: {0}")]
    UnknownAction(String),
    #[error("missing selector ref in zhihu flow step: {0}")]
    MissingSelectorRef(String),
    #[error("missing url ref in zhihu flow step: {0}")]
    MissingUrlRef(String),
    #[error("missing selector in zhihu flow: {0}")]
    MissingSelector(String),
    #[error("missing domain in zhihu flow: {0}")]
    MissingDomain(String),
    #[error("missing text source in zhihu flow step: {0}")]
    MissingTextSource(String),
    #[error("missing scroll target in zhihu flow step: {0}")]
    MissingScrollTarget(String),
    #[error("browser action failed at step {step}: {message}")]
    BrowserActionFailed { step: String, message: String },
    #[error("step {step} expected text containing `{expected}`, got `{actual}`")]
    ExpectedTextMissing {
        step: String,
        expected: String,
        actual: String,
    },
    #[error("step {step} expected text `{expected}`, got `{actual}`")]
    ExpectedTextMismatch {
        step: String,
        expected: String,
        actual: String,
    },
    #[error("step {step} did not return article url; cannot confirm article was published")]
    MissingPublishedUrl { step: String },
}

pub fn default_flow_path() -> PathBuf {
    super::default_skill_resource_path("zhihu_write_flow.json")
}

pub fn load_flow() -> Result<ZhihuFlow, ZhihuSkillError> {
    let path = default_flow_path();
    let contents = fs::read_to_string(&path)
        .map_err(|err| ZhihuSkillError::FlowLoad(format!("{} ({})", err, path.display())))?;
    serde_json::from_str(&contents)
        .map_err(|err| ZhihuSkillError::FlowLoad(format!("{} ({})", err, path.display())))
}

pub fn execute<T: Transport>(
    transport: &T,
    browser_tool: &BrowserPipeTool<T>,
    req: ZhihuWriteRequest,
) -> Result<ZhihuWriteResult, ZhihuSkillError> {
    validate_request(&req)?;
    let flow = load_flow()?;
    let mut final_url = None;
    let mut published_url = None;
    let mut publish_capture_step = None;

    for step in &flow.steps {
        if step.only_when_publish && !req.publish {
            continue;
        }

        transport
            .send(&AgentMessage::LogEntry {
                level: "info".to_string(),
                message: step.log_message.clone(),
            })
            .map_err(|err| ZhihuSkillError::BrowserActionFailed {
                step: step.name.clone(),
                message: err.to_string(),
            })?;

        let action = parse_action(&step.action)?;
        let expected_domain = resolve_domain(&flow, &step.expected_domain)?;
        let params = build_params(&flow, step, &req)?;
        let result = browser_tool
            .invoke(action, params, &expected_domain)
            .map_err(|err| ZhihuSkillError::BrowserActionFailed {
                step: step.name.clone(),
                message: err.to_string(),
            })?;

        if !result.success {
            return Err(ZhihuSkillError::BrowserActionFailed {
                step: step.name.clone(),
                message: result.data.to_string(),
            });
        }

        if step.capture_url {
            if let Some(url) = extract_url(&result.data) {
                if step.only_when_publish {
                    if is_published_article_url(&url) {
                        published_url = normalize_published_article_url(&url);
                    }
                } else {
                    final_url = Some(url);
                }
            }
            if step.only_when_publish {
                publish_capture_step = Some(step.name.clone());
            }
        }

        if let Some(expected) = step.expect_contains.as_deref() {
            let actual = extract_text(&result.data);
            if !actual.contains(expected) {
                return Err(ZhihuSkillError::ExpectedTextMissing {
                    step: step.name.clone(),
                    expected: expected.to_string(),
                    actual,
                });
            }
        }

        if let Some(source) = step.expect_text_source.as_deref() {
            let expected = resolve_text_source(&req, source)?.to_string();
            let actual = extract_text(&result.data);
            if actual.is_empty() && step.allow_empty_text {
                continue;
            }
            if actual != expected {
                return Err(ZhihuSkillError::ExpectedTextMismatch {
                    step: step.name.clone(),
                    expected,
                    actual,
                });
            }
        }
    }

    if req.publish {
        final_url = Some(
            published_url.ok_or_else(|| ZhihuSkillError::MissingPublishedUrl {
                step: publish_capture_step.unwrap_or_else(|| "publish_complete".to_string()),
            })?,
        );
    }

    Ok(ZhihuWriteResult {
        summary: build_summary(&req, final_url.as_deref()),
        published: req.publish,
        final_url,
    })
}

fn validate_request(req: &ZhihuWriteRequest) -> Result<(), ZhihuSkillError> {
    if req.title.trim().is_empty() {
        return Err(ZhihuSkillError::EmptyTitle);
    }
    if req.body.trim().is_empty() {
        return Err(ZhihuSkillError::EmptyBody);
    }
    Ok(())
}

fn parse_action(name: &str) -> Result<Action, ZhihuSkillError> {
    match name {
        "click" => Ok(Action::Click),
        "type" => Ok(Action::Type),
        "navigate" => Ok(Action::Navigate),
        "getText" => Ok(Action::GetText),
        "getHtml" => Ok(Action::GetHtml),
        "waitForSelector" => Ok(Action::WaitForSelector),
        "scrollTo" => Ok(Action::ScrollTo),
        other => Err(ZhihuSkillError::UnknownAction(other.to_string())),
    }
}

fn resolve_domain(flow: &ZhihuFlow, key: &str) -> Result<String, ZhihuSkillError> {
    flow.domains
        .get(key)
        .cloned()
        .ok_or_else(|| ZhihuSkillError::MissingDomain(key.to_string()))
}

fn resolve_selector<'a>(flow: &'a ZhihuFlow, key: &str) -> Result<&'a str, ZhihuSkillError> {
    flow.selectors
        .get(key)
        .map(String::as_str)
        .ok_or_else(|| ZhihuSkillError::MissingSelector(key.to_string()))
}

fn resolve_text_source<'a>(
    req: &'a ZhihuWriteRequest,
    source: &str,
) -> Result<&'a str, ZhihuSkillError> {
    match source {
        "title" => Ok(req.title.as_str()),
        "body" => Ok(req.body.as_str()),
        other => Err(ZhihuSkillError::MissingTextSource(other.to_string())),
    }
}

fn build_params(
    flow: &ZhihuFlow,
    step: &FlowStep,
    req: &ZhihuWriteRequest,
) -> Result<Value, ZhihuSkillError> {
    match step.action.as_str() {
        "navigate" => {
            let url_ref = step
                .url_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingUrlRef(step.name.clone()))?;
            let url = match url_ref {
                "entry_url" => flow.entry_url.as_str(),
                "editor_url" => flow.editor_url.as_str(),
                other => {
                    return Err(ZhihuSkillError::MissingUrlRef(format!(
                        "{}:{}",
                        step.name, other
                    )))
                }
            };
            Ok(json!({ "url": url }))
        }
        "click" => {
            let selector_ref = step
                .selector_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingSelectorRef(step.name.clone()))?;
            let selector = resolve_selector(flow, selector_ref)?;
            let mut params = serde_json::Map::new();
            params.insert("selector".to_string(), Value::String(selector.to_string()));
            if let Some(wait_after_ms) = step.wait_after_ms {
                params.insert("wait_after".to_string(), Value::from(wait_after_ms));
            }
            Ok(Value::Object(params))
        }
        "type" => {
            let selector_ref = step
                .selector_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingSelectorRef(step.name.clone()))?;
            let selector = resolve_selector(flow, selector_ref)?;
            let text_source = step
                .text_source
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingTextSource(step.name.clone()))?;
            let text = resolve_text_source(req, text_source)?;
            Ok(json!({
                "selector": selector,
                "text": text,
                "clear_first": step.clear_first,
            }))
        }
        "getText" => {
            let selector_ref = step
                .selector_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingSelectorRef(step.name.clone()))?;
            let selector = resolve_selector(flow, selector_ref)?;
            Ok(json!({ "selector": selector }))
        }
        "getHtml" => {
            let selector_ref = step
                .selector_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingSelectorRef(step.name.clone()))?;
            let selector = resolve_selector(flow, selector_ref)?;
            let mut params = serde_json::Map::new();
            params.insert("selector".to_string(), Value::String(selector.to_string()));
            if let Some(outer) = step.outer {
                params.insert("outer".to_string(), Value::Bool(outer));
            }
            Ok(Value::Object(params))
        }
        "waitForSelector" => {
            let selector_ref = step
                .selector_ref
                .as_deref()
                .ok_or_else(|| ZhihuSkillError::MissingSelectorRef(step.name.clone()))?;
            let selector = resolve_selector(flow, selector_ref)?;
            let mut params = serde_json::Map::new();
            params.insert("selector".to_string(), Value::String(selector.to_string()));
            if let Some(timeout_ms) = step.timeout_ms {
                params.insert("timeout_ms".to_string(), Value::from(timeout_ms));
            }
            Ok(Value::Object(params))
        }
        "scrollTo" => {
            if let Some(selector_ref) = step.selector_ref.as_deref() {
                let selector = resolve_selector(flow, selector_ref)?;
                return Ok(json!({ "selector": selector }));
            }
            if step.x.is_none() && step.y.is_none() {
                return Err(ZhihuSkillError::MissingScrollTarget(step.name.clone()));
            }
            let mut params = serde_json::Map::new();
            if let Some(x) = step.x {
                params.insert("x".to_string(), Value::from(x));
            }
            if let Some(y) = step.y {
                params.insert("y".to_string(), Value::from(y));
            }
            Ok(Value::Object(params))
        }
        other => Err(ZhihuSkillError::UnknownAction(other.to_string())),
    }
}

pub fn extract_text(data: &Value) -> String {
    data.get("text")
        .and_then(Value::as_str)
        .unwrap_or_default()
        .trim()
        .to_string()
}

fn extract_url(data: &Value) -> Option<String> {
    data.get("url")
        .and_then(Value::as_str)
        .map(str::trim)
        .filter(|url| !url.is_empty())
        .map(ToOwned::to_owned)
}

fn is_published_article_url(url: &str) -> bool {
    normalize_published_article_url(url).is_some()
}

fn normalize_published_article_url(url: &str) -> Option<String> {
    let trimmed = url.trim();
    if !trimmed.starts_with(ZHIHU_ARTICLE_URL_PREFIX) {
        return None;
    }
    if trimmed.ends_with(ZHIHU_ARTICLE_EDIT_SUFFIX) {
        return Some(
            trimmed
                .trim_end_matches(ZHIHU_ARTICLE_EDIT_SUFFIX)
                .to_string(),
        );
    }
    Some(trimmed.to_string())
}

fn build_summary(req: &ZhihuWriteRequest, final_url: Option<&str>) -> String {
    if req.publish {
        let url = final_url.expect("publish flow must provide final_url before building summary");
        format!("知乎文章已发布：{} ({url})", req.title.trim())
    } else {
        format!("知乎文章草稿已填充：{}", req.title.trim())
    }
}
