//! Go analyzer implementation.

use crate::analyzers::Analyzer;
use crate::analyzers::go::orchestration::analyze_go_file;
use crate::analyzers::go::parser::parse_source;
use crate::core::ast::Ast;
use crate::core::{ComplexityMetrics, FileMetrics, Language};
use anyhow::Result;
use std::path::PathBuf;
use tracing::{debug, debug_span};

pub struct GoAnalyzer {
    complexity_threshold: u32,
}

impl GoAnalyzer {
    pub fn new() -> Self {
        Self {
            complexity_threshold: 10,
        }
    }

    pub fn with_threshold(mut self, threshold: u32) -> Self {
        self.complexity_threshold = threshold;
        self
    }
}

impl Default for GoAnalyzer {
    fn default() -> Self {
        Self::new()
    }
}

impl Analyzer for GoAnalyzer {
    fn parse(&self, content: &str, path: PathBuf) -> Result<Ast> {
        let _span = debug_span!("parse_go_file", path = %path.display()).entered();
        let go_ast = parse_source(content, &path)?;

        debug!(
            path = %path.display(),
            bytes = content.len(),
            "Parsed Go file"
        );

        Ok(Ast::Go(go_ast))
    }

    fn analyze(&self, ast: &Ast) -> FileMetrics {
        match ast {
            Ast::Go(go_ast) => analyze_go_file(go_ast, self.complexity_threshold),
            _ => empty_go_metrics(),
        }
    }

    fn language(&self) -> Language {
        Language::Go
    }
}

fn empty_go_metrics() -> FileMetrics {
    FileMetrics {
        path: PathBuf::new(),
        language: Language::Go,
        complexity: ComplexityMetrics::default(),
        debt_items: vec![],
        dependencies: vec![],
        duplications: vec![],
        total_lines: 0,
        module_scope: None,
        classes: None,
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::analyzers::get_analyzer;

    #[test]
    fn test_go_analyzer_language() {
        let analyzer = GoAnalyzer::new();
        assert_eq!(analyzer.language(), Language::Go);
    }

    #[test]
    fn test_go_analyzer_factory() {
        let analyzer = get_analyzer(Language::Go);
        assert_eq!(analyzer.language(), Language::Go);
    }

    #[test]
    fn test_analyze_simple_go_file() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package main

import "fmt"

func main() {
    fmt.Println("hello")
}
"#;
        let ast = analyzer.parse(source, PathBuf::from("main.go")).unwrap();
        let metrics = analyzer.analyze(&ast);

        assert_eq!(metrics.language, Language::Go);
        assert_eq!(metrics.dependencies[0].name, "fmt");
        assert_eq!(metrics.complexity.functions.len(), 1);
        assert_eq!(metrics.complexity.functions[0].name, "main");
    }

    #[test]
    fn test_analyze_go_method_visibility_and_tests() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package service

func (h *Handler) Serve() {}
func TestServe(t *testing.T) {}
"#;
        let ast = analyzer
            .parse(source, PathBuf::from("handler_test.go"))
            .unwrap();
        let metrics = analyzer.analyze(&ast);

        assert_eq!(metrics.complexity.functions[0].name, "Handler.Serve");
        assert_eq!(
            metrics.complexity.functions[0].visibility,
            Some("public".to_string())
        );
        assert!(metrics.complexity.functions[1].is_test);
    }

    #[test]
    fn test_analyze_go_complexity_debt() {
        let analyzer = GoAnalyzer::new().with_threshold(2);
        let source = r#"package service

func decide(a, b bool) int {
    if a || b {
        return 1
    }
    for i := 0; i < 3; i++ {
        if b {
            return i
        }
    }
    return 0
}
"#;
        let ast = analyzer.parse(source, PathBuf::from("service.go")).unwrap();
        let metrics = analyzer.analyze(&ast);

        assert!(metrics.complexity.functions[0].cyclomatic > 2);
        assert!(!metrics.debt_items.is_empty());
    }

    #[test]
    fn test_go_test_function_skips_complexity_debt() {
        let analyzer = GoAnalyzer::new().with_threshold(1);
        let source = r#"package service

func TestDecide(t *testing.T) {
    if true {
        t.Fatal("failed")
    }
}
"#;
        let ast = analyzer
            .parse(source, PathBuf::from("service_test.go"))
            .unwrap();
        let metrics = analyzer.analyze(&ast);

        assert!(metrics.complexity.functions[0].is_test);
        assert!(metrics.debt_items.is_empty());
    }

    #[test]
    fn test_generated_go_file_suppresses_debt() {
        let analyzer = GoAnalyzer::new().with_threshold(1);
        let source = r#"// Code generated by protoc. DO NOT EDIT.
package api

func Generated(a bool) int {
    if a {
        return 1
    }
    return 0
}
"#;
        let ast = analyzer
            .parse(source, PathBuf::from("service.pb.go"))
            .unwrap();
        let metrics = analyzer.analyze(&ast);

        assert_eq!(metrics.complexity.functions.len(), 1);
        assert!(metrics.debt_items.is_empty());
    }

    #[test]
    fn test_go_analyzer_records_raw_call_dependencies() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package service

func Serve() {
    helper()
    fmt.Println("hello")
}

func helper() {}
"#;
        let ast = analyzer.parse(source, PathBuf::from("service.go")).unwrap();
        let metrics = analyzer.analyze(&ast);
        let serve = metrics
            .complexity
            .functions
            .iter()
            .find(|function| function.name == "Serve")
            .unwrap();

        assert_eq!(
            serve.call_dependencies,
            Some(vec!["helper".to_string(), "fmt.Println".to_string()])
        );
    }

    #[test]
    fn test_go_purity_signals() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package service

func add(a int) int {
    return a + 1
}

func run(ch chan int) {
    go add(1)
    ch <- 1
}
"#;
        let ast = analyzer.parse(source, PathBuf::from("service.go")).unwrap();
        let metrics = analyzer.analyze(&ast);
        let add = metrics
            .complexity
            .functions
            .iter()
            .find(|function| function.name == "add")
            .unwrap();
        let run = metrics
            .complexity
            .functions
            .iter()
            .find(|function| function.name == "run")
            .unwrap();

        assert_eq!(add.is_pure, Some(true));
        assert_eq!(run.is_pure, Some(false));
        assert!(
            run.detected_patterns
                .as_ref()
                .unwrap()
                .contains(&"go-statement".to_string())
        );
    }

    #[test]
    fn test_go_advanced_debt_signals() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package service

func Load(ch chan int) error {
    if err != nil { return err }
    if err != nil { return err }
    if err != nil { return err }
    value, _ := parse()
    _ = value
    go worker()
    ch <- 1
    defer recover()
    panic("failed")
}
"#;
        let ast = analyzer.parse(source, PathBuf::from("service.go")).unwrap();
        let metrics = analyzer.analyze(&ast);
        let load = metrics
            .complexity
            .functions
            .iter()
            .find(|function| function.name == "Load")
            .unwrap();
        let patterns = load.detected_patterns.as_ref().unwrap();

        assert!(patterns.contains(&"repetitive-error-handling".to_string()));
        assert!(patterns.contains(&"swallowed-error".to_string()));
        assert!(patterns.contains(&"goroutine-without-synchronization".to_string()));
        assert!(patterns.contains(&"channel-operation".to_string()));
        assert!(patterns.contains(&"recover-without-handling".to_string()));
        assert!(patterns.contains(&"panic-in-production".to_string()));
        assert_eq!(load.error_swallowing_count, Some(1));
        assert!(
            metrics
                .debt_items
                .iter()
                .any(|item| item.id.starts_with("go-swallowed-error-"))
        );
    }

    #[test]
    fn test_go_mutation_and_defer_loop_signals() {
        let analyzer = GoAnalyzer::new();
        let source = r#"package service

type State struct { count int }
var shared int

func (s *State) Run(items map[string]int) {
    for key := range items {
        defer close()
        s.count = 1
        items[key] = 2
        shared = 3
    }
}
"#;
        let ast = analyzer.parse(source, PathBuf::from("service.go")).unwrap();
        let metrics = analyzer.analyze(&ast);
        let run = metrics
            .complexity
            .functions
            .iter()
            .find(|function| function.name == "State.Run")
            .unwrap();
        let patterns = run.detected_patterns.as_ref().unwrap();

        assert!(patterns.contains(&"defer-in-loop".to_string()));
        assert!(patterns.contains(&"pointer-receiver-mutation".to_string()));
        assert!(patterns.contains(&"collection-mutation".to_string()));
        assert!(patterns.contains(&"package-global-mutation".to_string()));
    }

    #[test]
    fn test_generated_go_file_suppresses_advanced_debt() {
        let analyzer = GoAnalyzer::new();
        let source = r#"// Code generated by mockgen. DO NOT EDIT.
package mock

func Generated() {
    value, _ := parse()
    _ = value
    panic("generated")
}
"#;
        let ast = analyzer
            .parse(source, PathBuf::from("mock_service.go"))
            .unwrap();
        let metrics = analyzer.analyze(&ast);

        assert!(metrics.debt_items.is_empty());
        assert!(
            metrics.complexity.functions[0]
                .detected_patterns
                .as_ref()
                .is_some_and(|patterns| patterns.contains(&"swallowed-error".to_string()))
        );
    }
}
