package main

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

	"github.com/steveyegge/beads/internal/merge"
)

func TestParseConflicts(t *testing.T) {
	tests := []struct {
		name           string
		content        string
		wantConflicts  int
		wantCleanLines int
		wantErr        bool
	}{
		{
			name: "no conflicts",
			content: `{"id":"bd-1","title":"Issue 1"}
{"id":"bd-2","title":"Issue 2"}`,
			wantConflicts:  0,
			wantCleanLines: 2,
			wantErr:        false,
		},
		{
			name: "single conflict",
			content: `{"id":"bd-1","title":"Issue 1"}
<<<<<<< HEAD
{"id":"bd-2","title":"Issue 2 local"}
=======
{"id":"bd-2","title":"Issue 2 remote"}
>>>>>>> branch
{"id":"bd-3","title":"Issue 3"}`,
			wantConflicts:  1,
			wantCleanLines: 2,
			wantErr:        false,
		},
		{
			name: "multiple conflicts",
			content: `<<<<<<< HEAD
{"id":"bd-1","title":"Local 1"}
=======
{"id":"bd-1","title":"Remote 1"}
>>>>>>> branch
{"id":"bd-2","title":"Clean line"}
<<<<<<< HEAD
{"id":"bd-3","title":"Local 3"}
=======
{"id":"bd-3","title":"Remote 3"}
>>>>>>> other-branch`,
			wantConflicts:  2,
			wantCleanLines: 1,
			wantErr:        false,
		},
		{
			name: "unclosed conflict",
			content: `<<<<<<< HEAD
{"id":"bd-1","title":"Local"}
=======
{"id":"bd-1","title":"Remote"}`,
			wantConflicts:  0,
			wantCleanLines: 0,
			wantErr:        true,
		},
		{
			name: "nested conflict error",
			content: `<<<<<<< HEAD
<<<<<<< HEAD
{"id":"bd-1","title":"Nested"}
=======
{"id":"bd-1","title":"Remote"}
>>>>>>> branch`,
			wantConflicts:  0,
			wantCleanLines: 0,
			wantErr:        true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			conflicts, cleanLines, err := parseConflicts(tt.content)

			if tt.wantErr {
				if err == nil {
					t.Error("Expected error but got none")
				}
				return
			}

			if err != nil {
				t.Errorf("Unexpected error: %v", err)
				return
			}

			if len(conflicts) != tt.wantConflicts {
				t.Errorf("Got %d conflicts, want %d", len(conflicts), tt.wantConflicts)
			}

			if len(cleanLines) != tt.wantCleanLines {
				t.Errorf("Got %d clean lines, want %d", len(cleanLines), tt.wantCleanLines)
			}
		})
	}
}

func TestParseConflictsLabels(t *testing.T) {
	content := `<<<<<<< HEAD
{"id":"bd-1","title":"Local"}
=======
{"id":"bd-1","title":"Remote"}
>>>>>>> feature-branch`

	conflicts, _, err := parseConflicts(content)
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

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

	if conflicts[0].LeftLabel != "HEAD" {
		t.Errorf("Expected left label 'HEAD', got %q", conflicts[0].LeftLabel)
	}

	if conflicts[0].RightLabel != "feature-branch" {
		t.Errorf("Expected right label 'feature-branch', got %q", conflicts[0].RightLabel)
	}

	if len(conflicts[0].LeftSide) != 1 {
		t.Errorf("Expected 1 left line, got %d", len(conflicts[0].LeftSide))
	}

	if len(conflicts[0].RightSide) != 1 {
		t.Errorf("Expected 1 right line, got %d", len(conflicts[0].RightSide))
	}
}

func TestResolveConflict(t *testing.T) {
	tests := []struct {
		name       string
		conflict   conflictRegion
		wantIssueID string
		wantResContains string
	}{
		{
			name: "merge same issue different titles",
			conflict: conflictRegion{
				StartLine: 1,
				EndLine:   5,
				LeftSide:  []string{`{"id":"bd-1","title":"Local Title","updated_at":"2024-01-02T00:00:00Z"}`},
				RightSide: []string{`{"id":"bd-1","title":"Remote Title","updated_at":"2024-01-01T00:00:00Z"}`},
				LeftLabel: "HEAD",
				RightLabel: "branch",
			},
			wantIssueID:     "bd-1",
			wantResContains: "merged",
		},
		{
			name: "left only valid JSON",
			conflict: conflictRegion{
				StartLine: 1,
				EndLine:   5,
				LeftSide:  []string{`{"id":"bd-1","title":"Valid"}`},
				RightSide: []string{`not valid json`},
				LeftLabel: "HEAD",
				RightLabel: "branch",
			},
			wantIssueID:     "bd-1",
			wantResContains: "left_only_valid",
		},
		{
			name: "right only valid JSON",
			conflict: conflictRegion{
				StartLine: 1,
				EndLine:   5,
				LeftSide:  []string{`invalid json here`},
				RightSide: []string{`{"id":"bd-2","title":"Valid"}`},
				LeftLabel: "HEAD",
				RightLabel: "branch",
			},
			wantIssueID:     "bd-2",
			wantResContains: "right_only_valid",
		},
		{
			name: "both unparseable",
			conflict: conflictRegion{
				StartLine: 1,
				EndLine:   5,
				LeftSide:  []string{`not json`},
				RightSide: []string{`also not json`},
				LeftLabel: "HEAD",
				RightLabel: "branch",
			},
			wantIssueID:     "",
			wantResContains: "kept_both_unparseable",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, info := resolveConflict(tt.conflict, 1)

			if tt.wantIssueID != "" && info.IssueID != tt.wantIssueID {
				t.Errorf("Got issue ID %q, want %q", info.IssueID, tt.wantIssueID)
			}

			if !strings.Contains(info.Resolution, tt.wantResContains) {
				t.Errorf("Resolution %q doesn't contain %q", info.Resolution, tt.wantResContains)
			}
		})
	}
}

func TestMergeIssueConflict(t *testing.T) {
	t.Run("title picks later updated_at", func(t *testing.T) {
		left := merge.Issue{
			ID:        "bd-1",
			Title:     "Old Title",
			UpdatedAt: "2024-01-01T00:00:00Z",
		}
		right := merge.Issue{
			ID:        "bd-1",
			Title:     "New Title",
			UpdatedAt: "2024-01-02T00:00:00Z",
		}

		result := mergeIssueConflict(left, right)

		if result.Title != "New Title" {
			t.Errorf("Expected title 'New Title', got %q", result.Title)
		}
	})

	t.Run("closed status wins", func(t *testing.T) {
		left := merge.Issue{
			ID:     "bd-1",
			Status: "open",
		}
		right := merge.Issue{
			ID:     "bd-1",
			Status: "closed",
		}

		result := mergeIssueConflict(left, right)

		if result.Status != "closed" {
			t.Errorf("Expected status 'closed', got %q", result.Status)
		}
	})

	t.Run("higher priority wins", func(t *testing.T) {
		left := merge.Issue{
			ID:       "bd-1",
			Priority: 2,
		}
		right := merge.Issue{
			ID:       "bd-1",
			Priority: 1,
		}

		result := mergeIssueConflict(left, right)

		if result.Priority != 1 {
			t.Errorf("Expected priority 1, got %d", result.Priority)
		}
	})

	t.Run("notes concatenate when different", func(t *testing.T) {
		left := merge.Issue{
			ID:    "bd-1",
			Notes: "Note A",
		}
		right := merge.Issue{
			ID:    "bd-1",
			Notes: "Note B",
		}

		result := mergeIssueConflict(left, right)

		if !strings.Contains(result.Notes, "Note A") || !strings.Contains(result.Notes, "Note B") {
			t.Errorf("Expected concatenated notes, got %q", result.Notes)
		}
	})

	t.Run("dependencies union", func(t *testing.T) {
		left := merge.Issue{
			ID: "bd-1",
			Dependencies: []merge.Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-2", Type: "blocks"},
			},
		}
		right := merge.Issue{
			ID: "bd-1",
			Dependencies: []merge.Dependency{
				{IssueID: "bd-1", DependsOnID: "bd-3", Type: "blocks"},
			},
		}

		result := mergeIssueConflict(left, right)

		if len(result.Dependencies) != 2 {
			t.Errorf("Expected 2 dependencies, got %d", len(result.Dependencies))
		}
	})
}

func TestTimeHelpers(t *testing.T) {
	t.Run("isTimeAfterStr", func(t *testing.T) {
		tests := []struct {
			t1, t2 string
			want   bool
		}{
			{"2024-01-02T00:00:00Z", "2024-01-01T00:00:00Z", true},
			{"2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z", false},
			{"2024-01-01T00:00:00Z", "2024-01-01T00:00:00Z", false},
			{"2024-01-01T00:00:00Z", "", true},
			{"", "2024-01-01T00:00:00Z", false},
			{"", "", false},
		}

		for _, tt := range tests {
			got := isTimeAfterStr(tt.t1, tt.t2)
			if got != tt.want {
				t.Errorf("isTimeAfterStr(%q, %q) = %v, want %v", tt.t1, tt.t2, got, tt.want)
			}
		}
	})

	t.Run("maxTimeStr", func(t *testing.T) {
		tests := []struct {
			t1, t2, want string
		}{
			{"2024-01-02T00:00:00Z", "2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z"},
			{"2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z", "2024-01-02T00:00:00Z"},
			{"2024-01-01T00:00:00Z", "", "2024-01-01T00:00:00Z"},
			{"", "2024-01-01T00:00:00Z", "2024-01-01T00:00:00Z"},
			{"", "", ""},
		}

		for _, tt := range tests {
			got := maxTimeStr(tt.t1, tt.t2)
			if got != tt.want {
				t.Errorf("maxTimeStr(%q, %q) = %q, want %q", tt.t1, tt.t2, got, tt.want)
			}
		}
	})

	t.Run("pickByUpdatedAt", func(t *testing.T) {
		tests := []struct {
			left, right, leftTime, rightTime, want string
		}{
			{"A", "B", "2024-01-02T00:00:00Z", "2024-01-01T00:00:00Z", "A"},
			{"A", "B", "2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z", "B"},
			{"Same", "Same", "2024-01-01T00:00:00Z", "2024-01-02T00:00:00Z", "Same"},
		}

		for _, tt := range tests {
			got := pickByUpdatedAt(tt.left, tt.right, tt.leftTime, tt.rightTime)
			if got != tt.want {
				t.Errorf("pickByUpdatedAt(%q, %q, ...) = %q, want %q", tt.left, tt.right, got, tt.want)
			}
		}
	})
}

func TestResolveConflictsEndToEnd(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-resolve-test-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	// Create a file with conflicts
	conflictFile := filepath.Join(tmpDir, "test.jsonl")
	content := `{"id":"bd-1","title":"Clean issue"}
<<<<<<< HEAD
{"id":"bd-2","title":"Local version","updated_at":"2024-01-02T00:00:00Z"}
=======
{"id":"bd-2","title":"Remote version","updated_at":"2024-01-01T00:00:00Z"}
>>>>>>> remote-branch
{"id":"bd-3","title":"Another clean issue"}`

	if err := os.WriteFile(conflictFile, []byte(content), 0644); err != nil {
		t.Fatalf("Failed to write test file: %v", err)
	}

	// Parse conflicts
	conflicts, cleanLines, err := parseConflicts(content)
	if err != nil {
		t.Fatalf("Failed to parse conflicts: %v", err)
	}

	if len(conflicts) != 1 {
		t.Errorf("Expected 1 conflict, got %d", len(conflicts))
	}

	if len(cleanLines) != 2 {
		t.Errorf("Expected 2 clean lines, got %d", len(cleanLines))
	}

	// Resolve the conflict
	resolved, info := resolveConflict(conflicts[0], 1)
	if info.Resolution != "merged" {
		t.Errorf("Expected resolution 'merged', got %q", info.Resolution)
	}

	if info.IssueID != "bd-2" {
		t.Errorf("Expected issue ID 'bd-2', got %q", info.IssueID)
	}

	// The resolved content should contain the local title (later updated_at)
	if len(resolved) != 1 {
		t.Fatalf("Expected 1 resolved line, got %d", len(resolved))
	}

	if !strings.Contains(resolved[0], "Local version") {
		t.Errorf("Expected resolved content to contain 'Local version', got %q", resolved[0])
	}
}
