use serde::{Deserialize, Serialize};
use regex::Regex;
use ropey::Rope;
use std::path::PathBuf;
use std::process::{Command, Stdio};
use tower_lsp::lsp_types::*;

use crate::server::LspServer;
use crate::utils::command::is_command_valid;

use super::{AbstractLinterConfiguration, LinterStatus};

#[derive(Debug, Serialize, Deserialize)]
pub struct VerilatorConfiguration {
    pub language_id: String,
    pub linter: VerilatorLinter,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct VerilatorLinter {
    pub name: String,
    pub exe_name: String,
    /// 目前是否启动
    pub enabled: bool,
    pub path: String,
    pub args: Vec<String>,
}

impl VerilatorLinter {
    fn new(invoker: &str, args: Vec<String>) -> Self {
        Self {
            name: "verilator".to_string(),
            exe_name: invoker.to_string(),
            enabled: false,
            path: invoker.to_string(),
            args,
        }
    }
}


/// convert captured severity string to DiagnosticSeverity
fn verilator_severity(severity: &str) -> Option<DiagnosticSeverity> {
    match severity {
        "Error" => Some(DiagnosticSeverity::ERROR),
        s if s.starts_with("Warning") => Some(DiagnosticSeverity::WARNING),
        // NOTE: afaik, verilator doesn't have an info or hint severity
        _ => Some(DiagnosticSeverity::INFORMATION),
    }
}

/// syntax checking using verilator --lint-only
fn verilator_syntax(
    rope: &Rope,
    file_path: PathBuf,
    verilator_syntax_path: &str,
    verilator_syntax_args: &[String],
) -> Option<Vec<Diagnostic>> {
    let mut child = Command::new(verilator_syntax_path)
        .stdin(Stdio::piped())
        .stderr(Stdio::piped())
        .stdout(Stdio::piped())
        .args(verilator_syntax_args)
        .arg(file_path.to_str()?)
        .spawn()
        .ok()?;

    static RE: std::sync::OnceLock<Regex> = std::sync::OnceLock::new();
    let re = RE.get_or_init(|| {
        Regex::new(
            r"%(?P<severity>Error|Warning)(-(?P<warning_type>[A-Z0-9_]+))?: (?P<filepath>[^:]+):(?P<line>\d+):((?P<col>\d+):)? ?(?P<message>.*)",
        )
        .unwrap()
    });
    // write file to stdin, read output from stdout
    rope.write_to(child.stdin.as_mut()?).ok()?;
    let output = child.wait_with_output().ok()?;
    if !output.status.success() {
        let mut diags: Vec<Diagnostic> = Vec::new();
        let raw_output = String::from_utf8(output.stderr).ok()?;
        let filtered_output = raw_output
            .lines()
            .filter(|line| line.starts_with('%'))
            .collect::<Vec<&str>>();
        for error in filtered_output {
            let caps = match re.captures(error) {
                Some(caps) => caps,
                None => continue,
            };

            // check if diagnostic is for this file, since verilator can provide diagnostics for
            // included files
            if caps.name("filepath")?.as_str() != file_path.to_str().unwrap_or("") {
                continue;
            }
            let severity = verilator_severity(caps.name("severity")?.as_str());
            let line: u32 = caps.name("line")?.as_str().to_string().parse().ok()?;
            let col: u32 = caps.name("col").map_or("1", |m| m.as_str()).parse().ok()?;
            let pos = Position::new(line - 1, col - 1);
            let msg = match severity {
                Some(DiagnosticSeverity::ERROR) => caps.name("message")?.as_str().to_string(),
                Some(DiagnosticSeverity::WARNING) => format!(
                    "{}: {}",
                    caps.name("warning_type")?.as_str(),
                    caps.name("message")?.as_str()
                ),
                _ => "".to_string(),
            };
            diags.push(Diagnostic::new(
                Range::new(pos, pos),
                severity,
                None,
                Some("verilator".to_string()),
                msg,
                None,
                None,
            ));
        }
        Some(diags)
    } else {
        None
    }
}


impl AbstractLinterConfiguration for VerilatorConfiguration {
    fn new(language_id: &str, invoker: &str, args: Vec<String>) -> Self {
        VerilatorConfiguration {
            language_id: language_id.to_string(),
            linter: VerilatorLinter::new(invoker, args)
        }
    }
    
    fn provide_diagnostics(
        &self,
        uri: &Url,
        #[allow(unused)]
        rope: &Rope,
        server: &LspServer
    ) -> Option<Vec<Diagnostic>> {
        let mut diagnostics = Vec::<Diagnostic>::new();

        Some(diagnostics)
    }

    fn get_linter_path(&self) -> &str {
        &self.linter.path
    }

    fn get_windows_extension(&self) -> &str {
        "exe"
    }

    fn linter_status(&self) -> LinterStatus {
        match self.language_id.as_str() {
            "verilog" => {
                let invoke_name = self.get_invoke_name("verilator");
                let available = is_command_valid(&invoke_name);
                LinterStatus {
                    tool_name: "verilator".to_string(),
                    available,
                    invoke_name
                }
            }

            "systemverilog" => {
                let invoke_name = self.get_invoke_name("verilator");
                let available = is_command_valid(&invoke_name);
                LinterStatus {
                    tool_name: "verilator".to_string(),
                    available,
                    invoke_name
                }
            }

            // verilator 不支持 vhdl
            _ => {
                LinterStatus::default()
            }
        }
    }
}