package buildkite

import (
	"context"
	"errors"
	"testing"
	"time"

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

// MockBuildkiteLogsClient for testing
type MockBuildkiteLogsClient struct {
	DownloadAndCacheFunc func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error)
}

func (m *MockBuildkiteLogsClient) DownloadAndCache(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
	if m.DownloadAndCacheFunc != nil {
		return m.DownloadAndCacheFunc(ctx, org, pipeline, build, job, cacheTTL, forceRefresh)
	}
	return "/tmp/test.parquet", nil
}

var _ BuildkiteLogsClient = (*MockBuildkiteLogsClient)(nil)

func TestParseCacheTTL(t *testing.T) {
	tests := []struct {
		name     string
		input    string
		expected time.Duration
	}{
		{
			name:     "empty string",
			input:    "",
			expected: 30 * time.Second,
		},
		{
			name:     "valid duration",
			input:    "5m",
			expected: 5 * time.Minute,
		},
		{
			name:     "invalid duration",
			input:    "invalid",
			expected: 30 * time.Second,
		},
		{
			name:     "seconds",
			input:    "45s",
			expected: 45 * time.Second,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := parseCacheTTL(tt.input)
			require.Equal(t, tt.expected, result)
		})
	}
}

func TestValidateSearchPattern(t *testing.T) {
	tests := []struct {
		name    string
		pattern string
		wantErr bool
	}{
		{
			name:    "valid pattern",
			pattern: "error",
			wantErr: false,
		},
		{
			name:    "valid regex",
			pattern: "ERROR.*failed",
			wantErr: false,
		},
		{
			name:    "invalid regex",
			pattern: "[",
			wantErr: true,
		},
		{
			name:    "empty pattern",
			pattern: "",
			wantErr: false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := validateSearchPattern(tt.pattern)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestSearchLogsHandler(t *testing.T) {
	assert := require.New(t)
	ctx := context.Background()

	mockClient := &MockBuildkiteLogsClient{
		DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
			assert.Equal("test-org", org)
			assert.Equal("test-pipeline", pipeline)
			assert.Equal("123", build)
			assert.Equal("job-456", job)
			return "/tmp/test.parquet", nil
		},
	}

	_, handler, _ := SearchLogs(mockClient)

	t.Run("invalid regex pattern", func(t *testing.T) {
		params := SearchLogsParams{
			JobLogsBaseParams: JobLogsBaseParams{
				OrgSlug:      "test-org",
				PipelineSlug: "test-pipeline",
				BuildNumber:  "123",
				JobID:        "job-456",
			},
			Pattern: "[", // Invalid regex
		}

		result, err := handler(ctx, mcp.CallToolRequest{}, params)
		assert.NoError(err)
		textContent, ok := result.Content[0].(mcp.TextContent)
		assert.True(ok)
		assert.Contains(textContent.Text, "invalid regex pattern")
	})

	t.Run("client error", func(t *testing.T) {
		errorClient := &MockBuildkiteLogsClient{
			DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
				return "", errors.New("download failed")
			},
		}

		_, errorHandler, _ := SearchLogs(errorClient)

		params := SearchLogsParams{
			JobLogsBaseParams: JobLogsBaseParams{
				OrgSlug:      "test-org",
				PipelineSlug: "test-pipeline",
				BuildNumber:  "123",
				JobID:        "job-456",
			},
			Pattern: "error",
		}

		result, err := errorHandler(ctx, mcp.CallToolRequest{}, params)
		assert.NoError(err)
		textContent, ok := result.Content[0].(mcp.TextContent)
		assert.True(ok)
		assert.Contains(textContent.Text, "Failed to create log reader")
	})
}

func TestTailLogsHandler(t *testing.T) {
	assert := require.New(t)
	ctx := context.Background()

	mockClient := &MockBuildkiteLogsClient{
		DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
			return "/tmp/test.parquet", nil
		},
	}

	_, handler, _ := TailLogs(mockClient)

	t.Run("default tail value", func(t *testing.T) {
		params := TailLogsParams{
			JobLogsBaseParams: JobLogsBaseParams{
				OrgSlug:      "test-org",
				PipelineSlug: "test-pipeline",
				BuildNumber:  "123",
				JobID:        "job-456",
			},
			Tail: 0, // Should default to 10
		}

		// This will fail due to the parquet file not existing, but we can check the parameters
		result, err := handler(ctx, mcp.CallToolRequest{}, params)
		assert.NoError(err)
		textContent, ok := result.Content[0].(mcp.TextContent)
		assert.True(ok)
		assert.Contains(textContent.Text, "Failed to get file info")
	})
}

func TestGetLogsInfoHandler(t *testing.T) {
	assert := require.New(t)
	ctx := context.Background()

	mockClient := &MockBuildkiteLogsClient{
		DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
			return "/tmp/test.parquet", nil
		},
	}

	_, handler, _ := GetLogsInfo(mockClient)

	params := JobLogsBaseParams{
		OrgSlug:      "test-org",
		PipelineSlug: "test-pipeline",
		BuildNumber:  "123",
		JobID:        "job-456",
	}

	// This will fail due to the parquet file not existing, but we can test the flow
	result, err := handler(ctx, mcp.CallToolRequest{}, params)
	assert.NoError(err)
	textContent, ok := result.Content[0].(mcp.TextContent)
	assert.True(ok)
	assert.Contains(textContent.Text, "Failed to get file info")
}

func TestReadLogsHandler(t *testing.T) {
	assert := require.New(t)
	ctx := context.Background()

	mockClient := &MockBuildkiteLogsClient{
		DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
			return "/tmp/test.parquet", nil
		},
	}

	_, handler, _ := ReadLogs(mockClient)

	params := ReadLogsParams{
		JobLogsBaseParams: JobLogsBaseParams{
			OrgSlug:      "test-org",
			PipelineSlug: "test-pipeline",
			BuildNumber:  "123",
			JobID:        "job-456",
		},
		Seek:  0,
		Limit: 100,
	}

	// This will fail due to the parquet file not existing, but we can test the flow
	result, err := handler(ctx, mcp.CallToolRequest{}, params)
	assert.NoError(err)
	textContent, ok := result.Content[0].(mcp.TextContent)
	assert.True(ok)
	assert.Contains(textContent.Text, "Failed to read entries")
}

func TestNewParquetReader(t *testing.T) {
	assert := require.New(t)
	ctx := context.Background()

	t.Run("successful creation", func(t *testing.T) {
		mockClient := &MockBuildkiteLogsClient{
			DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
				assert.Equal("test-org", org)
				assert.Equal("test-pipeline", pipeline)
				assert.Equal("123", build)
				assert.Equal("job-456", job)
				assert.Equal(5*time.Minute, cacheTTL)
				assert.True(forceRefresh)
				return "/tmp/test.parquet", nil
			},
		}

		params := JobLogsBaseParams{
			OrgSlug:      "test-org",
			PipelineSlug: "test-pipeline",
			BuildNumber:  "123",
			JobID:        "job-456",
			CacheTTL:     "5m",
			ForceRefresh: true,
		}

		// This will succeed in creating the reader but fail later when trying to read
		// the non-existent parquet file, but we can verify the client was called correctly
		reader, err := newParquetReader(ctx, mockClient, params)
		assert.NoError(err)   // Creation succeeds
		assert.NotNil(reader) // Reader is created
	})

	t.Run("client error", func(t *testing.T) {
		mockClient := &MockBuildkiteLogsClient{
			DownloadAndCacheFunc: func(ctx context.Context, org, pipeline, build, job string, cacheTTL time.Duration, forceRefresh bool) (string, error) {
				return "", errors.New("download failed")
			},
		}

		params := JobLogsBaseParams{
			OrgSlug:      "test-org",
			PipelineSlug: "test-pipeline",
			BuildNumber:  "123",
			JobID:        "job-456",
		}

		reader, err := newParquetReader(ctx, mockClient, params)
		assert.Error(err)
		assert.Nil(reader)
		assert.Contains(err.Error(), "failed to download/cache logs")
	})
}
