//go:build integration
// +build integration

package main

import (
	"bytes"
	"context"
	"encoding/json"
	"os"
	"os/exec"
	"path/filepath"
	"testing"
	"time"

	"github.com/steveyegge/beads/internal/storage/sqlite"
	"github.com/steveyegge/beads/internal/types"
)

// TestGitPullSyncIntegration tests the full git pull sync scenario
// Verifies that after git pull, both daemon and non-daemon modes pick up changes automatically
func TestGitPullSyncIntegration(t *testing.T) {
	if testing.Short() {
		t.Skip("Skipping integration test in short mode")
	}

	// Create temp directory for test repositories
	tempDir := t.TempDir()

	// Create "remote" repository
	remoteDir := filepath.Join(tempDir, "remote")
	if err := os.MkdirAll(remoteDir, 0750); err != nil {
		t.Fatalf("Failed to create remote dir: %v", err)
	}

	// Initialize remote git repo
	runGitCmd(t, remoteDir, "init", "--bare", "-b", "master")

	// Create "clone1" repository
	clone1Dir := filepath.Join(tempDir, "clone1")
	runGitCmd(t, tempDir, "clone", remoteDir, clone1Dir)
	configureGit(t, clone1Dir)

	// Initialize beads in clone1
	clone1BeadsDir := filepath.Join(clone1Dir, ".beads")
	if err := os.MkdirAll(clone1BeadsDir, 0750); err != nil {
		t.Fatalf("Failed to create .beads dir: %v", err)
	}

	clone1DBPath := filepath.Join(clone1BeadsDir, "test.db")
	clone1Store := newTestStore(t, clone1DBPath)
	defer clone1Store.Close()

	ctx := context.Background()
	if err := clone1Store.SetMetadata(ctx, "issue_prefix", "test"); err != nil {
		t.Fatalf("Failed to set prefix: %v", err)
	}

	// Create and close an issue in clone1
	issue := &types.Issue{
		Title:     "Test sync issue",
		Status:    types.StatusOpen,
		Priority:  1,
		IssueType: types.TypeTask,
		CreatedAt: time.Now(),
		UpdatedAt: time.Now(),
	}
	if err := clone1Store.CreateIssue(ctx, issue, "test-user"); err != nil {
		t.Fatalf("Failed to create issue: %v", err)
	}
	issueID := issue.ID

	// Close the issue
	if err := clone1Store.CloseIssue(ctx, issueID, "Test completed", "test-user"); err != nil {
		t.Fatalf("Failed to close issue: %v", err)
	}

	// Export to JSONL
	jsonlPath := filepath.Join(clone1BeadsDir, "issues.jsonl")
	if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
		t.Fatalf("Failed to export: %v", err)
	}

	// Commit and push from clone1
	runGitCmd(t, clone1Dir, "add", ".beads")
	runGitCmd(t, clone1Dir, "commit", "-m", "Add closed issue")
	runGitCmd(t, clone1Dir, "push", "origin", "master")

	// Create "clone2" repository
	clone2Dir := filepath.Join(tempDir, "clone2")
	runGitCmd(t, tempDir, "clone", remoteDir, clone2Dir)
	configureGit(t, clone2Dir)

	// Initialize empty database in clone2
	clone2BeadsDir := filepath.Join(clone2Dir, ".beads")
	clone2DBPath := filepath.Join(clone2BeadsDir, "test.db")
	clone2Store := newTestStore(t, clone2DBPath)
	defer clone2Store.Close()

	if err := clone2Store.SetMetadata(ctx, "issue_prefix", "test"); err != nil {
		t.Fatalf("Failed to set prefix: %v", err)
	}

	// Import the existing JSONL (simulating initial sync)
	clone2JSONLPath := filepath.Join(clone2BeadsDir, "issues.jsonl")
	if err := importJSONLToStore(ctx, clone2Store, clone2DBPath, clone2JSONLPath); err != nil {
		t.Fatalf("Failed to import: %v", err)
	}

	// Verify issue exists and is closed
	verifyIssueClosed(t, clone2Store, issueID)

	// Note: We don't commit in clone2 - it stays clean as a read-only consumer

	// Now test git pull scenario: Clone1 makes a change (update priority)
	if err := clone1Store.UpdateIssue(ctx, issueID, map[string]interface{}{
		"priority": 0,
	}, "test-user"); err != nil {
		t.Fatalf("Failed to update issue: %v", err)
	}

	if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
		t.Fatalf("Failed to export after update: %v", err)
	}

	runGitCmd(t, clone1Dir, "add", ".beads/issues.jsonl")
	runGitCmd(t, clone1Dir, "commit", "-m", "Update priority")
	runGitCmd(t, clone1Dir, "push", "origin", "master")

	// Clone2 pulls the change
	runGitCmd(t, clone2Dir, "pull")

	// Test auto-import in non-daemon mode
	t.Run("NonDaemonAutoImport", func(t *testing.T) {
		// Use a temporary local store for this test
		localStore := newTestStore(t, clone2DBPath)
		defer localStore.Close()

		// Manually import to simulate auto-import behavior
		startTime := time.Now()
		if err := importJSONLToStore(ctx, localStore, clone2DBPath, clone2JSONLPath); err != nil {
			t.Fatalf("Failed to auto-import: %v", err)
		}
		elapsed := time.Since(startTime)

		// Verify priority was updated
		issue, err := localStore.GetIssue(ctx, issueID)
		if err != nil {
			t.Fatalf("Failed to get issue: %v", err)
		}
		if issue.Priority != 0 {
			t.Errorf("Expected priority 0 after auto-import, got %d", issue.Priority)
		}

		// Verify performance: import should be fast
		if elapsed > 100*time.Millisecond {
			t.Logf("Info: import took %v", elapsed)
		}
	})

	// Test bd sync --import-only command
	t.Run("BdSyncCommand", func(t *testing.T) {
		// Make another change in clone1 (change priority back to 1)
		if err := clone1Store.UpdateIssue(ctx, issueID, map[string]interface{}{
			"priority": 1,
		}, "test-user"); err != nil {
			t.Fatalf("Failed to update issue: %v", err)
		}

		if err := exportIssuesToJSONL(ctx, clone1Store, jsonlPath); err != nil {
			t.Fatalf("Failed to export: %v", err)
		}

		runGitCmd(t, clone1Dir, "add", ".beads/issues.jsonl")
		runGitCmd(t, clone1Dir, "commit", "-m", "Update priority")
		runGitCmd(t, clone1Dir, "push", "origin", "master")

		// Clone2 pulls
		runGitCmd(t, clone2Dir, "pull")

		// Use a fresh store for import
		syncStore := newTestStore(t, clone2DBPath)
		defer syncStore.Close()

		// Manually trigger import via in-process equivalent
		if err := importJSONLToStore(ctx, syncStore, clone2DBPath, clone2JSONLPath); err != nil {
			t.Fatalf("Failed to import via sync: %v", err)
		}

		// Verify priority was updated back to 1
		issue, err := syncStore.GetIssue(ctx, issueID)
		if err != nil {
			t.Fatalf("Failed to get issue: %v", err)
		}
		if issue.Priority != 1 {
			t.Errorf("Expected priority 1, got %d", issue.Priority)
		}
	})
}

// Helper functions

func runGitCmd(t *testing.T, dir string, args ...string) {
	cmd := exec.Command("git", args...)
	cmd.Dir = dir
	cmd.Env = append(os.Environ(), "GIT_COMMITTER_DATE=2024-01-01T00:00:00", "GIT_AUTHOR_DATE=2024-01-01T00:00:00")
	output, err := cmd.CombinedOutput()
	if err != nil {
		t.Fatalf("git %v failed in %s: %v\n%s", args, dir, err, output)
	}
}

func configureGit(t *testing.T, dir string) {
	runGitCmd(t, dir, "config", "user.email", "test@example.com")
	runGitCmd(t, dir, "config", "user.name", "Test User")
	runGitCmd(t, dir, "config", "pull.rebase", "false")

	// Create .gitignore to prevent test database files from being tracked
	gitignorePath := filepath.Join(dir, ".gitignore")
	gitignoreContent := `# Test database files
*.db
*.db-journal
*.db-wal
*.db-shm
`
	if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0644); err != nil {
		t.Fatalf("Failed to create .gitignore: %v", err)
	}
}

func exportIssuesToJSONL(ctx context.Context, store *sqlite.SQLiteStorage, jsonlPath string) error {
	issues, err := store.SearchIssues(ctx, "", types.IssueFilter{})
	if err != nil {
		return err
	}

	// Populate dependencies
	allDeps, err := store.GetAllDependencyRecords(ctx)
	if err != nil {
		return err
	}
	for _, issue := range issues {
		issue.Dependencies = allDeps[issue.ID]
		labels, _ := store.GetLabels(ctx, issue.ID)
		issue.Labels = labels
	}

	f, err := os.Create(jsonlPath)
	if err != nil {
		return err
	}
	defer f.Close()

	encoder := json.NewEncoder(f)
	for _, issue := range issues {
		if err := encoder.Encode(issue); err != nil {
			return err
		}
	}

	return nil
}

func importJSONLToStore(ctx context.Context, store *sqlite.SQLiteStorage, dbPath, jsonlPath string) error {
	data, err := os.ReadFile(jsonlPath)
	if err != nil {
		return err
	}

	// Use the autoimport package's AutoImportIfNewer function
	// For testing, we'll directly parse and import
	var issues []*types.Issue
	decoder := json.NewDecoder(bytes.NewReader(data))
	for decoder.More() {
		var issue types.Issue
		if err := decoder.Decode(&issue); err != nil {
			return err
		}
		issues = append(issues, &issue)
	}

	// Import each issue
	for _, issue := range issues {
		existing, _ := store.GetIssue(ctx, issue.ID)
		if existing != nil {
			// Update
			updates := map[string]interface{}{
				"status":   issue.Status,
				"priority": issue.Priority,
			}
			if err := store.UpdateIssue(ctx, issue.ID, updates, "import"); err != nil {
				return err
			}
		} else {
			// Create
			if err := store.CreateIssue(ctx, issue, "import"); err != nil {
				return err
			}
		}
	}

	// Set last_import_time metadata so staleness check works
	if err := store.SetMetadata(ctx, "last_import_time", time.Now().Format(time.RFC3339)); err != nil {
		return err
	}

	return nil
}

func verifyIssueClosed(t *testing.T, store *sqlite.SQLiteStorage, issueID string) {
	issue, err := store.GetIssue(context.Background(), issueID)
	if err != nil {
		t.Fatalf("Failed to get issue %s: %v", issueID, err)
	}
	if issue.Status != types.StatusClosed {
		t.Errorf("Expected issue %s to be closed, got status %s", issueID, issue.Status)
	}
}
