use super::*;
use ckg_core::EdgeKind;

#[test]
fn extracts_basic_go() {
    let src = r#"
        package svc
        // helper does work.
        func helper() int { return 1 }
        func caller() int { return helper() }
    "#;
    let out = extract("repo", Path::new("svc/x.go"), src).unwrap();
    let names: Vec<&str> = out.symbols.iter().map(|s| s.name.as_str()).collect();
    for n in ["helper", "caller"] {
        assert!(names.contains(&n), "missing {n}: {names:?}");
    }
    // Doc captured for helper
    let helper = out.symbols.iter().find(|s| s.name == "helper").unwrap();
    assert!(
        helper.doc.contains("helper does work"),
        "doc: {:?}",
        helper.doc
    );
    // file-local call resolution at conf 1.0
    assert!(
        out.edges.iter().any(|e| matches!(e.kind, EdgeKind::Calls)
            && e.dst.ends_with("helper:function")
            && (e.confidence - 1.0).abs() < 1e-3),
        "missing helper-call at 1.0"
    );
}

#[test]
fn extracts_struct_and_method() {
    let src = r#"
        package m
        type Foo struct{}
        func (r *Foo) bar() {}
    "#;
    let out = extract("repo", Path::new("m/foo.go"), src).unwrap();
    let foo = out.symbols.iter().find(|s| s.name == "Foo").unwrap();
    assert_eq!(foo.kind, Kind::Struct);
    let bar = out.symbols.iter().find(|s| s.name == "bar").unwrap();
    assert_eq!(bar.kind, Kind::Method);
    // qname includes the receiver type
    assert!(bar.qname.contains("Foo.bar"), "qname: {}", bar.qname);
    // Defines edge from Foo → bar
    let defines = out
        .edges
        .iter()
        .any(|e| matches!(e.kind, EdgeKind::Defines) && e.src == foo.id && e.dst == bar.id);
    assert!(defines, "no Defines edge Foo → bar");
}

#[test]
fn imports_with_alias() {
    let src = r#"
        package m
        import (
            "fmt"
            j "encoding/json"
        )
        func use() {
            fmt.Println("hi")
            j.Marshal(nil)
        }
    "#;
    let out = extract("repo", Path::new("m/u.go"), src).unwrap();
    let imports: Vec<&str> = out
        .edges
        .iter()
        .filter(|e| matches!(e.kind, EdgeKind::Imports))
        .map(|e| e.dst.as_str())
        .collect();
    assert!(imports.contains(&"fmt"));
    assert!(imports.contains(&"encoding/json"));

    // Aliased call: j.Marshal → encoding/json.Marshal at 0.9
    let aliased = out.edges.iter().any(|e| {
        matches!(e.kind, EdgeKind::Calls)
            && e.dst == "encoding/json.Marshal"
            && (e.confidence - 0.9).abs() < 1e-3
    });
    assert!(aliased, "expected j.Marshal → encoding/json.Marshal at 0.9");

    // Auto-aliased call: fmt.Println → fmt.Println at 0.9
    let fmt_call = out.edges.iter().any(|e| {
        matches!(e.kind, EdgeKind::Calls)
            && e.dst == "fmt.Println"
            && (e.confidence - 0.9).abs() < 1e-3
    });
    assert!(fmt_call, "expected fmt.Println auto-alias at 0.9");
}

#[test]
fn embedded_struct_emits_extends() {
    let src = r#"
        package m
        type Base struct{}
        type Child struct {
            Base
            Name string
        }
    "#;
    let out = extract("repo", Path::new("m/c.go"), src).unwrap();
    let child = out.symbols.iter().find(|s| s.name == "Child").unwrap();
    let ext = out
        .edges
        .iter()
        .any(|e| matches!(e.kind, EdgeKind::Extends) && e.src == child.id && e.dst == "Base");
    assert!(ext, "missing Extends Child → Base for embedded struct");
}

#[test]
fn type_aware_dispatch_via_var_or_short_decl() {
    let src = r#"
        package m
        type Foo struct{}
        func (f *Foo) bar() {}
        func use_var() {
            var x Foo
            x.bar()
        }
        func use_short() {
            y := &Foo{}
            y.bar()
        }
    "#;
    let out = extract("repo", Path::new("m/td.go"), src).unwrap();
    let typed = out
        .edges
        .iter()
        .filter(|e| {
            matches!(e.kind, EdgeKind::Calls)
                && e.dst == "Foo.bar"
                && (e.confidence - 0.7).abs() < 1e-3
        })
        .count();
    assert!(
        typed >= 2,
        "expected ≥2 typed-dispatch Foo.bar edges (var + short), got {typed}"
    );
}

#[test]
fn test_funcs_get_test_tag() {
    let src = r#"
        package m
        import "testing"
        func TestFoo(t *testing.T) {
            Helper()
        }
        func BenchmarkBar(b *testing.B) {}
        func ExampleBaz() {}
        func realWork() {}
    "#;
    let out = extract("repo", Path::new("m/x_test.go"), src).unwrap();
    let tagged: Vec<&str> = out
        .symbols
        .iter()
        .filter(|s| s.doc.contains("@test"))
        .map(|s| s.name.as_str())
        .collect();
    for n in ["TestFoo", "BenchmarkBar", "ExampleBaz"] {
        assert!(tagged.contains(&n), "missing @test on {n}: {tagged:?}");
    }
    let real = out.symbols.iter().find(|s| s.name == "realWork").unwrap();
    assert!(!real.doc.contains("@test"), "realWork shouldn't be @test");
}

#[test]
fn interface_methods_emitted() {
    let src = r#"
        package m
        type Greeter interface {
            Greet() string
            Wave()
        }
    "#;
    let out = extract("repo", Path::new("m/i.go"), src).unwrap();
    let iface = out.symbols.iter().find(|s| s.name == "Greeter").unwrap();
    assert_eq!(iface.kind, Kind::Interface);
    let methods: Vec<&str> = out
        .symbols
        .iter()
        .filter(|s| s.kind == Kind::Method)
        .map(|s| s.name.as_str())
        .collect();
    assert!(methods.contains(&"Greet"), "missing Greet method symbol");
    assert!(methods.contains(&"Wave"), "missing Wave method symbol");
}
