package buildkite

import (
	"context"
	"fmt"
	"io"
	"net/http"
	"strings"
	"testing"

	"github.com/buildkite/go-buildkite/v4"
	"github.com/mark3labs/mcp-go/mcp"
	"github.com/stretchr/testify/require"
)

type MockTestExecutionsClient struct {
	GetFailedExecutionsFunc func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error)
}

func (m *MockTestExecutionsClient) GetFailedExecutions(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
	if m.GetFailedExecutionsFunc != nil {
		return m.GetFailedExecutionsFunc(ctx, org, slug, runID, opt)
	}
	return nil, nil, nil
}

var _ TestExecutionsClient = (*MockTestExecutionsClient)(nil)

func TestGetFailedExecutions(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	failedExecutions := []buildkite.FailedExecution{
		{
			ExecutionID:   "exec-1",
			RunID:         "run-123",
			TestID:        "test-456",
			TestName:      "Test Case 1",
			FailureReason: "Assertion failed",
			Duration:      1.5,
		},
		{
			ExecutionID:   "exec-2",
			RunID:         "run-123",
			TestID:        "test-789",
			TestName:      "Test Case 2",
			FailureReason: "Timeout",
			Duration:      30.0,
		},
	}

	mockClient := &MockTestExecutionsClient{
		GetFailedExecutionsFunc: func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
			return failedExecutions, &buildkite.Response{
				Response: &http.Response{
					StatusCode: http.StatusOK,
				},
			}, nil
		},
	}

	tool, handler, _ := GetFailedTestExecutions(mockClient)

	// Test tool properties
	assert.Equal("get_failed_executions", tool.Name)
	assert.Equal("Get failed test executions for a specific test run in Buildkite Test Engine. Optionally get the expanded failure details such as full error messages and stack traces.", tool.Description)
	if tool.Annotations.ReadOnlyHint != nil {
		assert.True(*tool.Annotations.ReadOnlyHint)
	}

	// Test successful request
	request := createMCPRequest(t, map[string]any{
		"org_slug":                 "org",
		"test_suite_slug":          "suite1",
		"run_id":                   "run1",
		"include_failure_expanded": true,
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.NotNil(result)

	// Check the result contains failed execution data
	textContent := result.Content[0].(mcp.TextContent)
	assert.Contains(textContent.Text, "exec-1")
	assert.Contains(textContent.Text, "exec-2")
	assert.Contains(textContent.Text, "Test Case 1")
	assert.Contains(textContent.Text, "Assertion failed")
	assert.Contains(textContent.Text, "Timeout")
	// Should always have pagination metadata (defaults: page=1, per_page=25)
	assert.Contains(textContent.Text, `"page":1`)
	assert.Contains(textContent.Text, `"per_page":25`)
	assert.Contains(textContent.Text, `"total":2`)
}

func TestGetFailedExecutionsMissingOrg(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	mockClient := &MockTestExecutionsClient{}

	_, handler, _ := GetFailedTestExecutions(mockClient)

	request := createMCPRequest(t, map[string]any{
		"test_suite_slug": "suite1",
		"run_id":          "run1",
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.True(result.IsError)
	assert.Contains(result.Content[0].(mcp.TextContent).Text, "org")
}

func TestGetFailedExecutionsMissingTestSuiteSlug(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	mockClient := &MockTestExecutionsClient{}

	_, handler, _ := GetFailedTestExecutions(mockClient)

	request := createMCPRequest(t, map[string]any{
		"org_slug": "org",
		"run_id":   "run1",
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.True(result.IsError)
	assert.Contains(result.Content[0].(mcp.TextContent).Text, "test_suite_slug")
}

func TestGetFailedExecutionsMissingRunID(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	mockClient := &MockTestExecutionsClient{}

	_, handler, _ := GetFailedTestExecutions(mockClient)

	request := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.True(result.IsError)
	assert.Contains(result.Content[0].(mcp.TextContent).Text, "run_id")
}

func TestGetFailedExecutionsWithError(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	mockClient := &MockTestExecutionsClient{
		GetFailedExecutionsFunc: func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
			return []buildkite.FailedExecution{}, &buildkite.Response{}, fmt.Errorf("API error")
		},
	}

	_, handler, _ := GetFailedTestExecutions(mockClient)

	request := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.True(result.IsError)
	assert.Contains(result.Content[0].(mcp.TextContent).Text, "API error")
}

func TestGetFailedExecutionsHTTPError(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	mockClient := &MockTestExecutionsClient{
		GetFailedExecutionsFunc: func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
			resp := &http.Response{
				Request: &http.Request{
					Method: "GET",
					URL:    nil,
				},
				StatusCode: http.StatusNotFound,
				Body:       io.NopCloser(strings.NewReader("Failed executions not found")),
			}

			return []buildkite.FailedExecution{}, &buildkite.Response{
				Response: resp,
			}, &buildkite.ErrorResponse{Response: resp, Message: "Failed executions not found"}
		},
	}

	_, handler, _ := GetFailedTestExecutions(mockClient)

	request := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
	})

	result, err := handler(ctx, request)
	assert.NoError(err)
	assert.True(result.IsError)
	assert.Contains(result.Content[0].(mcp.TextContent).Text, "Failed executions not found")
}

func TestGetFailedExecutionsPagination(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	// Create 6 failed executions to test pagination
	failedExecutions := []buildkite.FailedExecution{
		{
			ExecutionID:   "exec-1",
			RunID:         "run-123",
			TestID:        "test-456",
			TestName:      "Test Case 1",
			FailureReason: "Assertion failed",
			Duration:      1.5,
		},
		{
			ExecutionID:   "exec-2",
			RunID:         "run-123",
			TestID:        "test-789",
			TestName:      "Test Case 2",
			FailureReason: "Timeout",
			Duration:      30.0,
		},
		{
			ExecutionID:   "exec-3",
			RunID:         "run-123",
			TestID:        "test-101",
			TestName:      "Test Case 3",
			FailureReason: "Network error",
			Duration:      5.0,
		},
		{
			ExecutionID:   "exec-4",
			RunID:         "run-123",
			TestID:        "test-102",
			TestName:      "Test Case 4",
			FailureReason: "Database error",
			Duration:      2.5,
		},
		{
			ExecutionID:   "exec-5",
			RunID:         "run-123",
			TestID:        "test-103",
			TestName:      "Test Case 5",
			FailureReason: "Memory leak",
			Duration:      10.0,
		},
		{
			ExecutionID:   "exec-6",
			RunID:         "run-123",
			TestID:        "test-104",
			TestName:      "Test Case 6",
			FailureReason: "Segmentation fault",
			Duration:      0.1,
		},
	}

	mockClient := &MockTestExecutionsClient{
		GetFailedExecutionsFunc: func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
			return failedExecutions, &buildkite.Response{
				Response: &http.Response{
					StatusCode: http.StatusOK,
				},
			}, nil
		},
	}

	tool, handler, _ := GetFailedTestExecutions(mockClient)
	assert.NotNil(tool)
	assert.NotNil(handler)

	// Test first page with page size of 2
	requestFirstPage := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
		"page":            float64(1),
		"perPage":         float64(2),
	})
	resultFirstPage, err := handler(ctx, requestFirstPage)
	assert.NoError(err)

	textContentFirstPage := resultFirstPage.Content[0].(mcp.TextContent)
	// Should contain first 2 executions
	assert.Contains(textContentFirstPage.Text, "exec-1")
	assert.Contains(textContentFirstPage.Text, "exec-2")
	assert.NotContains(textContentFirstPage.Text, "exec-3")
	assert.NotContains(textContentFirstPage.Text, "exec-4")
	// Should have pagination metadata
	assert.Contains(textContentFirstPage.Text, `"page":1`)
	assert.Contains(textContentFirstPage.Text, `"per_page":2`)
	assert.Contains(textContentFirstPage.Text, `"total":6`)
	assert.Contains(textContentFirstPage.Text, `"has_next":true`)
	assert.Contains(textContentFirstPage.Text, `"has_prev":false`)

	// Test second page with page size of 2
	requestSecondPage := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
		"page":            float64(2),
		"perPage":         float64(2),
	})
	resultSecondPage, err := handler(ctx, requestSecondPage)
	assert.NoError(err)

	textContentSecondPage := resultSecondPage.Content[0].(mcp.TextContent)
	// Should contain next 2 executions
	assert.NotContains(textContentSecondPage.Text, "exec-1")
	assert.NotContains(textContentSecondPage.Text, "exec-2")
	assert.Contains(textContentSecondPage.Text, "exec-3")
	assert.Contains(textContentSecondPage.Text, "exec-4")
	// Should have pagination metadata
	assert.Contains(textContentSecondPage.Text, `"page":2`)
	assert.Contains(textContentSecondPage.Text, `"per_page":2`)
	assert.Contains(textContentSecondPage.Text, `"total":6`)
	assert.Contains(textContentSecondPage.Text, `"has_next":true`)
	assert.Contains(textContentSecondPage.Text, `"has_prev":true`)

	// Test last page
	requestLastPage := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
		"page":            float64(3),
		"perPage":         float64(2),
	})
	resultLastPage, err := handler(ctx, requestLastPage)
	assert.NoError(err)

	textContentLastPage := resultLastPage.Content[0].(mcp.TextContent)
	// Should contain last 2 executions
	assert.Contains(textContentLastPage.Text, "exec-5")
	assert.Contains(textContentLastPage.Text, "exec-6")
	// Should have pagination metadata
	assert.Contains(textContentLastPage.Text, `"page":3`)
	assert.Contains(textContentLastPage.Text, `"per_page":2`)
	assert.Contains(textContentLastPage.Text, `"total":6`)
	assert.Contains(textContentLastPage.Text, `"has_next":false`)
	assert.Contains(textContentLastPage.Text, `"has_prev":true`)

	// Test page beyond available data
	requestBeyond := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
		"page":            float64(5),
		"perPage":         float64(2),
	})
	resultBeyond, err := handler(ctx, requestBeyond)
	assert.NoError(err)

	textContentBeyond := resultBeyond.Content[0].(mcp.TextContent)
	// Should contain empty items array
	assert.Contains(textContentBeyond.Text, `"items":[]`)
}

func TestGetFailedExecutionsLargePage(t *testing.T) {
	assert := require.New(t)

	ctx := context.Background()
	failedExecutions := []buildkite.FailedExecution{
		{
			ExecutionID:   "exec-1",
			RunID:         "run-123",
			TestID:        "test-456",
			TestName:      "Test Case 1",
			FailureReason: "Assertion failed",
			Duration:      1.5,
		},
		{
			ExecutionID:   "exec-2",
			RunID:         "run-123",
			TestID:        "test-789",
			TestName:      "Test Case 2",
			FailureReason: "Timeout",
			Duration:      30.0,
		},
	}

	mockClient := &MockTestExecutionsClient{
		GetFailedExecutionsFunc: func(ctx context.Context, org, slug, runID string, opt *buildkite.FailedExecutionsOptions) ([]buildkite.FailedExecution, *buildkite.Response, error) {
			return failedExecutions, &buildkite.Response{
				Response: &http.Response{
					StatusCode: http.StatusOK,
				},
			}, nil
		},
	}

	tool, handler, _ := GetFailedTestExecutions(mockClient)
	assert.NotNil(tool)
	assert.NotNil(handler)

	// Test with page size larger than available data
	requestLargePage := createMCPRequest(t, map[string]any{
		"org_slug":        "org",
		"test_suite_slug": "suite1",
		"run_id":          "run1",
		"page":            float64(1),
		"perPage":         float64(10),
	})
	resultLargePage, err := handler(ctx, requestLargePage)
	assert.NoError(err)

	textContentLargePage := resultLargePage.Content[0].(mcp.TextContent)
	// Should contain all executions
	assert.Contains(textContentLargePage.Text, "exec-1")
	assert.Contains(textContentLargePage.Text, "exec-2")
	// Should have pagination metadata
	assert.Contains(textContentLargePage.Text, `"page":1`)
	assert.Contains(textContentLargePage.Text, `"per_page":10`)
	assert.Contains(textContentLargePage.Text, `"total":2`)
	assert.Contains(textContentLargePage.Text, `"has_next":false`)
	assert.Contains(textContentLargePage.Text, `"has_prev":false`)
}
