package lsp

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"

	"github.com/marte-dev/marte-dev-tools/internal/index"
	"github.com/marte-dev/marte-dev-tools/internal/parser"
)

func TestInitProjectScan(t *testing.T) {
	// 1. Setup temp dir with files
	tmpDir, err := os.MkdirTemp("", "lsp_test")
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(tmpDir)

	// File 1: Definition
	if err := os.WriteFile(filepath.Join(tmpDir, "def.marte"), []byte("#package Test.Common\n+Target = { Class = C }"), 0644); err != nil {
		t.Fatal(err)
	}
	// File 2: Reference
	// +Source = { Class = C Link = Target }
	// Link = Target starts at index ...
	// #package Test.Common (21 chars including newline)
	// +Source = { Class = C Link = Target }
	// 012345678901234567890123456789012345
	// Previous offset was 29.
	// Now add 21? 
	// #package Test.Common\n
	// +Source = ...
	// So add 21 to Character? Or Line 1?
	// It's on Line 1 (0-based 1).
	if err := os.WriteFile(filepath.Join(tmpDir, "ref.marte"), []byte("#package Test.Common\n+Source = { Class = C Link = Target }"), 0644); err != nil {
		t.Fatal(err)
	}

	// 2. Initialize
	tree = index.NewProjectTree() // Reset global tree

	initParams := InitializeParams{RootPath: tmpDir}
	paramsBytes, _ := json.Marshal(initParams)

	msg := &JsonRpcMessage{
		Method: "initialize",
		Params: paramsBytes,
		ID:     1,
	}

	handleMessage(msg)

	// Query the reference in ref.marte at "Target"
	// Target starts at index 29 (0-based) on Line 1
	defParams := DefinitionParams{
		TextDocument: TextDocumentIdentifier{URI: "file://" + filepath.Join(tmpDir, "ref.marte")},
		Position:     Position{Line: 1, Character: 29},
	}

	res := handleDefinition(defParams)
	if res == nil {
		t.Fatal("Definition not found via LSP after initialization")
	}

	locs, ok := res.([]Location)
	if !ok {
		t.Fatalf("Expected []Location, got %T", res)
	}

	if len(locs) == 0 {
		t.Fatal("No locations found")
	}

	// Verify uri points to def.marte
	expectedURI := "file://" + filepath.Join(tmpDir, "def.marte")
	if locs[0].URI != expectedURI {
		t.Errorf("Expected URI %s, got %s", expectedURI, locs[0].URI)
	}
}

func TestHandleDefinition(t *testing.T) {
	// Reset tree for test
	tree = index.NewProjectTree()
	
	content := `
+MyObject = {
    Class = Type
}
+RefObject = {
    Class = Type
    RefField = MyObject
}
`
	path := "/test.marte"
	p := parser.NewParser(content)
	config, err := p.Parse()
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	tree.AddFile(path, config)
	tree.ResolveReferences()

	t.Logf("Refs: %d", len(tree.References))
	for _, r := range tree.References {
		t.Logf("  %s at %d:%d", r.Name, r.Position.Line, r.Position.Column)
	}

	// Test Go to Definition on MyObject reference
	params := DefinitionParams{
		TextDocument: TextDocumentIdentifier{URI: "file://" + path},
		Position:     Position{Line: 6, Character: 15}, // "MyObject" in RefField = MyObject
	}

	result := handleDefinition(params)
	if result == nil {
		t.Fatal("handleDefinition returned nil")
	}

	locations, ok := result.([]Location)
	if !ok {
		t.Fatalf("Expected []Location, got %T", result)
	}

	if len(locations) != 1 {
		t.Fatalf("Expected 1 location, got %d", len(locations))
	}

	if locations[0].Range.Start.Line != 1 { // +MyObject is on line 2 (0-indexed 1)
		t.Errorf("Expected definition on line 1, got %d", locations[0].Range.Start.Line)
	}
}

func TestHandleReferences(t *testing.T) {
	// Reset tree for test
	tree = index.NewProjectTree()
	
	content := `
+MyObject = {
    Class = Type
}
+RefObject = {
    Class = Type
    RefField = MyObject
}
+AnotherRef = {
    Ref = MyObject
}
`
	path := "/test.marte"
	p := parser.NewParser(content)
	config, err := p.Parse()
	if err != nil {
		t.Fatalf("Parse failed: %v", err)
	}
	tree.AddFile(path, config)
	tree.ResolveReferences()

	// Test Find References for MyObject (triggered from its definition)
	params := ReferenceParams{
		TextDocument: TextDocumentIdentifier{URI: "file://" + path},
		Position:     Position{Line: 1, Character: 1}, // "+MyObject"
		Context:      ReferenceContext{IncludeDeclaration: true},
	}

	locations := handleReferences(params)
	if len(locations) != 3 { // 1 declaration + 2 references
		t.Fatalf("Expected 3 locations, got %d", len(locations))
	}
}

func TestLSPFormatting(t *testing.T) {
	// Setup
    content := `
#package Proj.Main
   +Object={
Field=1
  }
`
    uri := "file:///test.marte"
    
    // Open (populate documents map)
    documents[uri] = content
    
    // Format
    params := DocumentFormattingParams{
        TextDocument: TextDocumentIdentifier{URI: uri},
    }
    
    edits := handleFormatting(params)
    
    if len(edits) != 1 {
        t.Fatalf("Expected 1 edit, got %d", len(edits))
    }
    
    newText := edits[0].NewText
    
    expected := `#package Proj.Main

+Object = {
  Field = 1
}
`
    // Normalize newlines for comparison just in case
    if strings.TrimSpace(strings.ReplaceAll(newText, "\r\n", "\n")) != strings.TrimSpace(strings.ReplaceAll(expected, "\r\n", "\n")) {
        t.Errorf("Formatting mismatch.\nExpected:\n%s\nGot:\n%s", expected, newText)
    }
}