// Test Rig-Level Custom Agent Support
//
// This integration test verifies that custom agents defined in rig-level
// settings/config.json are correctly loaded and used when spawning polecats.
// It creates a stub agent, configures it at the rig level, and verifies
// the agent is actually used via tmux session capture.

package config

import (
	"encoding/json"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"
)

// TestRigLevelCustomAgentIntegration tests end-to-end rig-level custom agent functionality.
// This test:
// 1. Creates a stub agent script that echoes identifiable output
// 2. Sets up a minimal town/rig with the custom agent configured
// 3. Verifies that BuildPolecatStartupCommand uses the custom agent
// 4. Optionally spawns a tmux session and verifies output (if tmux available)
func TestRigLevelCustomAgentIntegration(t *testing.T) {
	tmpDir := t.TempDir()

	// Create the stub agent script
	stubAgentPath := createStubAgent(t, tmpDir)

	// Set up town structure
	townRoot := filepath.Join(tmpDir, "town")
	rigName := "testrig"
	rigPath := filepath.Join(townRoot, rigName)

	setupTestTownWithCustomAgent(t, townRoot, rigName, stubAgentPath)

	// Test 1: Verify ResolveAgentConfig picks up the custom agent
	t.Run("ResolveAgentConfig uses rig-level agent", func(t *testing.T) {
		rc := ResolveAgentConfig(townRoot, rigPath)
		if rc == nil {
			t.Fatal("ResolveAgentConfig returned nil")
		}

		if rc.Command != stubAgentPath {
			t.Errorf("Expected command %q, got %q", stubAgentPath, rc.Command)
		}

		// Verify args are passed through
		if len(rc.Args) != 2 || rc.Args[0] != "--test-mode" || rc.Args[1] != "--stub" {
			t.Errorf("Expected args [--test-mode --stub], got %v", rc.Args)
		}
	})

	// Test 2: Verify BuildPolecatStartupCommand includes the custom agent
	t.Run("BuildPolecatStartupCommand uses custom agent", func(t *testing.T) {
		cmd := BuildPolecatStartupCommand(rigName, "test-polecat", rigPath, "")

		if !strings.Contains(cmd, stubAgentPath) {
			t.Errorf("Expected command to contain stub agent path %q, got: %s", stubAgentPath, cmd)
		}

		if !strings.Contains(cmd, "--test-mode") {
			t.Errorf("Expected command to contain --test-mode, got: %s", cmd)
		}

		// Verify environment variables are set
		if !strings.Contains(cmd, "GT_ROLE=polecat") {
			t.Errorf("Expected GT_ROLE=polecat in command, got: %s", cmd)
		}

		if !strings.Contains(cmd, "GT_POLECAT=test-polecat") {
			t.Errorf("Expected GT_POLECAT=test-polecat in command, got: %s", cmd)
		}
	})

	// Test 3: Verify ResolveAgentConfigWithOverride respects rig agents
	t.Run("ResolveAgentConfigWithOverride with rig agent", func(t *testing.T) {
		rc, agentName, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "stub-agent")
		if err != nil {
			t.Fatalf("ResolveAgentConfigWithOverride failed: %v", err)
		}

		if agentName != "stub-agent" {
			t.Errorf("Expected agent name 'stub-agent', got %q", agentName)
		}

		if rc.Command != stubAgentPath {
			t.Errorf("Expected command %q, got %q", stubAgentPath, rc.Command)
		}
	})

	// Test 4: Verify unknown agent override returns error
	t.Run("ResolveAgentConfigWithOverride unknown agent errors", func(t *testing.T) {
		_, _, err := ResolveAgentConfigWithOverride(townRoot, rigPath, "nonexistent-agent")
		if err == nil {
			t.Fatal("Expected error for nonexistent agent, got nil")
		}

		if !strings.Contains(err.Error(), "not found") {
			t.Errorf("Expected 'not found' error, got: %v", err)
		}
	})

	// Test 5: Tmux integration (skip if tmux not available)
	t.Run("TmuxSessionWithCustomAgent", func(t *testing.T) {
		if _, err := exec.LookPath("tmux"); err != nil {
			t.Skip("tmux not available, skipping session test")
		}

		testTmuxSessionWithStubAgent(t, tmpDir, stubAgentPath, rigName)
	})
}

// createStubAgent creates a bash script that simulates an AI agent.
// The script echoes identifiable output and handles simple Q&A.
func createStubAgent(t *testing.T, tmpDir string) string {
	t.Helper()

	stubScript := `#!/bin/bash
# Stub Agent for Integration Testing
# This simulates an AI agent with identifiable output

AGENT_NAME="STUB_AGENT"
AGENT_VERSION="1.0.0"

echo "=========================================="
echo "STUB_AGENT_STARTED"
echo "Agent: $AGENT_NAME v$AGENT_VERSION"
echo "Args: $@"
echo "Working Dir: $(pwd)"
echo "GT_ROLE: ${GT_ROLE:-not_set}"
echo "GT_POLECAT: ${GT_POLECAT:-not_set}"
echo "GT_RIG: ${GT_RIG:-not_set}"
echo "=========================================="

# Simple Q&A loop
while true; do
    echo ""
    echo "STUB_AGENT_READY"
    echo "Enter question (or 'exit' to quit):"
    
    # Read with timeout for non-interactive testing
    if read -t 5 question; then
        case "$question" in
            "exit"|"quit"|"q")
                echo "STUB_AGENT_EXITING"
                exit 0
                ;;
            "what is 2+2"*)
                echo "STUB_AGENT_ANSWER: 4"
                ;;
            "ping"*)
                echo "STUB_AGENT_ANSWER: pong"
                ;;
            "status"*)
                echo "STUB_AGENT_ANSWER: operational"
                ;;
            *)
                echo "STUB_AGENT_ANSWER: I received your question: $question"
                ;;
        esac
    else
        # Timeout - check if we should exit
        if [ -f "/tmp/stub_agent_stop_$$" ]; then
            echo "STUB_AGENT_STOPPING (signal file detected)"
            rm -f "/tmp/stub_agent_stop_$$"
            exit 0
        fi
    fi
done
`

	stubPath := filepath.Join(tmpDir, "stub-agent")
	if err := os.WriteFile(stubPath, []byte(stubScript), 0755); err != nil {
		t.Fatalf("Failed to create stub agent: %v", err)
	}

	return stubPath
}

// setupTestTownWithCustomAgent creates a minimal town/rig structure with a custom agent.
func setupTestTownWithCustomAgent(t *testing.T, townRoot, rigName, stubAgentPath string) {
	t.Helper()

	rigPath := filepath.Join(townRoot, rigName)

	// Create directory structure
	dirs := []string{
		filepath.Join(townRoot, "mayor"),
		filepath.Join(townRoot, "settings"),
		filepath.Join(rigPath, "settings"),
		filepath.Join(rigPath, "polecats"),
	}

	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("Failed to create directory %s: %v", dir, err)
		}
	}

	// Create town.json
	townConfig := map[string]interface{}{
		"type":       "town",
		"version":    2,
		"name":       "test-town",
		"created_at": time.Now().Format(time.RFC3339),
	}
	writeTownJSON(t, filepath.Join(townRoot, "mayor", "town.json"), townConfig)

	// Create town settings (empty, uses defaults)
	townSettings := map[string]interface{}{
		"type":          "town-settings",
		"version":       1,
		"default_agent": "claude",
	}
	writeTownJSON(t, filepath.Join(townRoot, "settings", "config.json"), townSettings)

	// Create rig settings with custom agent
	rigSettings := map[string]interface{}{
		"type":    "rig-settings",
		"version": 1,
		"agent":   "stub-agent",
		"agents": map[string]interface{}{
			"stub-agent": map[string]interface{}{
				"command": stubAgentPath,
				"args":    []string{"--test-mode", "--stub"},
			},
		},
	}
	writeTownJSON(t, filepath.Join(rigPath, "settings", "config.json"), rigSettings)

	// Create rigs.json
	rigsConfig := map[string]interface{}{
		"version": 1,
		"rigs": map[string]interface{}{
			rigName: map[string]interface{}{
				"git_url":  "https://github.com/test/testrepo.git",
				"added_at": time.Now().Format(time.RFC3339),
			},
		},
	}
	writeTownJSON(t, filepath.Join(townRoot, "mayor", "rigs.json"), rigsConfig)
}

// writeTownJSON writes a JSON config file.
func writeTownJSON(t *testing.T, path string, data interface{}) {
	t.Helper()

	jsonData, err := json.MarshalIndent(data, "", "  ")
	if err != nil {
		t.Fatalf("Failed to marshal JSON for %s: %v", path, err)
	}

	if err := os.WriteFile(path, jsonData, 0644); err != nil {
		t.Fatalf("Failed to write %s: %v", path, err)
	}
}

func pollForOutput(t *testing.T, sessionName, expected string, timeout time.Duration) (string, bool) {
	t.Helper()
	deadline := time.Now().Add(timeout)
	for time.Now().Before(deadline) {
		output := captureTmuxPane(t, sessionName, 50)
		if strings.Contains(output, expected) {
			return output, true
		}
		time.Sleep(100 * time.Millisecond)
	}
	return captureTmuxPane(t, sessionName, 50), false
}

func testTmuxSessionWithStubAgent(t *testing.T, tmpDir, stubAgentPath, rigName string) {
	t.Helper()

	sessionName := fmt.Sprintf("gt-test-pid%d-%d", os.Getpid(), time.Now().UnixNano())
	workDir := tmpDir

	exec.Command("tmux", "kill-session", "-t", sessionName).Run()

	defer func() {
		exec.Command("tmux", "kill-session", "-t", sessionName).Run()
	}()

	cmd := exec.Command("tmux", "new-session", "-d", "-s", sessionName, "-c", workDir)
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to create tmux session: %v", err)
	}

	envVars := map[string]string{
		"GT_ROLE":    "polecat",
		"GT_POLECAT": "test-polecat",
		"GT_RIG":     rigName,
	}

	for key, val := range envVars {
		cmd := exec.Command("tmux", "set-environment", "-t", sessionName, key, val)
		if err := cmd.Run(); err != nil {
			t.Logf("Warning: failed to set %s: %v", key, err)
		}
	}

	agentCmd := fmt.Sprintf("%s --test-mode --stub", stubAgentPath)
	cmd = exec.Command("tmux", "send-keys", "-t", sessionName, agentCmd, "Enter")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to send keys: %v", err)
	}

	output, found := pollForOutput(t, sessionName, "STUB_AGENT_STARTED", 12*time.Second)
	if !found {
		t.Skipf("stub agent output not detected; tmux capture unreliable. Output:\n%s", output)
	}

	if !strings.Contains(output, "GT_ROLE: polecat") {
		t.Logf("Warning: GT_ROLE not visible in agent output (tmux env may not propagate to subshell)")
	}

	cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "ping", "Enter")
	if err := cmd.Run(); err != nil {
		t.Fatalf("Failed to send ping: %v", err)
	}

	output, found = pollForOutput(t, sessionName, "STUB_AGENT_ANSWER: pong", 6*time.Second)
	if !found {
		t.Errorf("Expected 'pong' response, got:\n%s", output)
	}

	cmd = exec.Command("tmux", "send-keys", "-t", sessionName, "exit", "Enter")
	if err := cmd.Run(); err != nil {
		t.Logf("Warning: failed to send exit: %v", err)
	}

	output, found = pollForOutput(t, sessionName, "STUB_AGENT_EXITING", 3*time.Second)
	if !found {
		t.Logf("Note: Agent may have exited before capture. Output:\n%s", output)
	}

	t.Logf("Tmux session test completed successfully")
}

// captureTmuxPane captures the output from a tmux pane.
func captureTmuxPane(t *testing.T, sessionName string, lines int) string {
	t.Helper()

	cmd := exec.Command("tmux", "capture-pane", "-t", sessionName, "-p", "-S", fmt.Sprintf("-%d", lines))
	output, err := cmd.Output()
	if err != nil {
		t.Logf("Warning: failed to capture pane: %v", err)
		return ""
	}

	return string(output)
}

func waitForTmuxOutputContains(t *testing.T, sessionName, needle string, timeout time.Duration) (string, bool) {
	t.Helper()

	deadline := time.Now().Add(timeout)
	output := ""
	for time.Now().Before(deadline) {
		output = captureTmuxPane(t, sessionName, 200)
		if strings.Contains(output, needle) {
			return output, true
		}
		time.Sleep(250 * time.Millisecond)
	}
	return output, false
}

// TestRigAgentOverridesTownAgent verifies rig agents take precedence over town agents.
func TestRigAgentOverridesTownAgent(t *testing.T) {
	tmpDir := t.TempDir()

	townRoot := filepath.Join(tmpDir, "town")
	rigName := "testrig"
	rigPath := filepath.Join(townRoot, rigName)

	// Create directory structure
	dirs := []string{
		filepath.Join(townRoot, "mayor"),
		filepath.Join(townRoot, "settings"),
		filepath.Join(rigPath, "settings"),
	}

	for _, dir := range dirs {
		if err := os.MkdirAll(dir, 0755); err != nil {
			t.Fatalf("Failed to create directory %s: %v", dir, err)
		}
	}

	// Town settings with a custom agent
	townSettings := map[string]interface{}{
		"type":          "town-settings",
		"version":       1,
		"default_agent": "my-agent",
		"agents": map[string]interface{}{
			"my-agent": map[string]interface{}{
				"command": "/town/path/to/agent",
				"args":    []string{"--town-level"},
			},
		},
	}
	writeTownJSON(t, filepath.Join(townRoot, "settings", "config.json"), townSettings)

	// Rig settings with SAME agent name but different config (should override)
	rigSettings := map[string]interface{}{
		"type":    "rig-settings",
		"version": 1,
		"agent":   "my-agent",
		"agents": map[string]interface{}{
			"my-agent": map[string]interface{}{
				"command": "/rig/path/to/agent",
				"args":    []string{"--rig-level"},
			},
		},
	}
	writeTownJSON(t, filepath.Join(rigPath, "settings", "config.json"), rigSettings)

	// Create town.json
	townConfig := map[string]interface{}{
		"type":       "town",
		"version":    2,
		"name":       "test-town",
		"created_at": time.Now().Format(time.RFC3339),
	}
	writeTownJSON(t, filepath.Join(townRoot, "mayor", "town.json"), townConfig)

	// Resolve agent config
	rc := ResolveAgentConfig(townRoot, rigPath)
	if rc == nil {
		t.Fatal("ResolveAgentConfig returned nil")
	}

	// Rig agent should take precedence
	if rc.Command != "/rig/path/to/agent" {
		t.Errorf("Expected rig agent command '/rig/path/to/agent', got %q", rc.Command)
	}

	if len(rc.Args) != 1 || rc.Args[0] != "--rig-level" {
		t.Errorf("Expected rig args [--rig-level], got %v", rc.Args)
	}
}
