package handlers

import (
	"context"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

	"github.com/go-chi/chi/v5"
	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/port"
)

// mockCIProvider implements port.CIProvider for testing.
type mockCIProvider struct {
	pipelines map[string][]*domain.CIPipeline
	err       error
}

func newMockCIProvider() *mockCIProvider {
	return &mockCIProvider{pipelines: make(map[string][]*domain.CIPipeline)}
}

func (m *mockCIProvider) ActivateRepo(context.Context, string, string, string) (*domain.CIRepo, error) {
	return nil, m.err
}
func (m *mockCIProvider) DeactivateRepo(context.Context, string, string) error {
	return m.err
}
func (m *mockCIProvider) GetRepo(context.Context, string, string) (*domain.CIRepo, error) {
	return nil, m.err
}
func (m *mockCIProvider) ListRepos(context.Context) ([]*domain.CIRepo, error) {
	return nil, m.err
}
func (m *mockCIProvider) AddSecret(context.Context, string, string, domain.CISecret) error {
	return m.err
}
func (m *mockCIProvider) DeleteSecret(context.Context, string, string, string) error {
	return m.err
}

func (m *mockCIProvider) ListPipelines(_ context.Context, owner, repo string) ([]*domain.CIPipeline, error) {
	if m.err != nil {
		return nil, m.err
	}
	key := owner + "/" + repo
	p, ok := m.pipelines[key]
	if !ok {
		return nil, fmt.Errorf("repo not found: %s", key)
	}
	return p, nil
}

func (m *mockCIProvider) GetPipeline(_ context.Context, owner, repo string, number int64) (*domain.CIPipeline, error) {
	if m.err != nil {
		return nil, m.err
	}
	key := owner + "/" + repo
	for _, p := range m.pipelines[key] {
		if p.Number == number {
			return p, nil
		}
	}
	return nil, fmt.Errorf("pipeline %d not found", number)
}

func (m *mockCIProvider) TriggerBuild(_ context.Context, owner, repo, branch string) (int64, error) {
	if m.err != nil {
		return 0, m.err
	}
	return 1, nil
}

func (m *mockCIProvider) GetPipelineSteps(_ context.Context, owner, repo string, number int64) (*domain.CIPipelineSteps, error) {
	if m.err != nil {
		return nil, m.err
	}
	key := owner + "/" + repo
	// Check if pipeline exists
	for _, p := range m.pipelines[key] {
		if p.Number == number {
			return &domain.CIPipelineSteps{
				PipelineNumber: number,
				URL:            fmt.Sprintf("https://ci.example.com/%s/%d", key, number),
				Steps: []domain.CIPipelineStep{
					{ID: 1, Name: "clone", Status: "success", Duration: 5},
					{ID: 2, Name: "build", Status: "success", Duration: 30},
					{ID: 3, Name: "test", Status: "success", Duration: 15},
				},
			}, nil
		}
	}
	return nil, fmt.Errorf("pipeline %d not found", number)
}

func (m *mockCIProvider) RetryPipeline(_ context.Context, owner, repo string, number int64) (*domain.CIPipeline, error) {
	if m.err != nil {
		return nil, m.err
	}
	key := owner + "/" + repo
	for _, p := range m.pipelines[key] {
		if p.Number == number {
			// Simulate retry by returning the pipeline with running status
			return &domain.CIPipeline{
				ID:     p.ID,
				Number: p.Number,
				Status: "running",
				Event:  p.Event,
				Branch: p.Branch,
				Commit: p.Commit,
				Author: p.Author,
			}, nil
		}
	}
	return nil, fmt.Errorf("pipeline %d not found", number)
}

func setupInfraHandlerWithCI(ci port.CIProvider) chi.Router {
	h := NewInfrastructureHandler(nil, nil, nil, nil, ci, nil, InfrastructureConfig{
		DefaultGitOwner: "threesix",
		DefaultDomain:   "threesix.ai",
	})
	r := chi.NewRouter()
	r.Use(testAdminAuth)
	h.Mount(r)
	return r
}

func TestInfrastructureHandler_ListPipelines(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		ci := newMockCIProvider()
		ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
			{
				ID:      100,
				Number:  1,
				Status:  "success",
				Event:   "push",
				Branch:  "main",
				Commit:  "abc123",
				Message: "initial commit",
				Author:  "dev",
				Started: time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC),
			},
			{
				ID:     101,
				Number: 2,
				Status: "running",
				Event:  "push",
				Branch: "feature",
				Commit: "def456",
				Author: "dev",
			},
		}

		router := setupInfraHandlerWithCI(ci)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}
	})

	t.Run("ci not configured", func(t *testing.T) {
		router := setupInfraHandlerWithCI(nil)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})

	t.Run("repo not found", func(t *testing.T) {
		ci := newMockCIProvider()
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/missing/pipelines", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
		}
	})

	t.Run("invalid project id", func(t *testing.T) {
		ci := newMockCIProvider()
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/INVALID!/pipelines", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})
}

func TestInfrastructureHandler_GetPipeline(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		ci := newMockCIProvider()
		ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
			{
				ID:       100,
				Number:   5,
				Status:   "success",
				Event:    "push",
				Branch:   "main",
				Commit:   "abc123",
				Message:  "fix bug",
				Author:   "dev",
				Started:  time.Date(2025, 1, 15, 10, 0, 0, 0, time.UTC),
				Finished: time.Date(2025, 1, 15, 10, 5, 0, 0, time.UTC),
			},
		}

		router := setupInfraHandlerWithCI(ci)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/5", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusOK)
		}
	})

	t.Run("ci not configured", func(t *testing.T) {
		router := setupInfraHandlerWithCI(nil)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/1", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})

	t.Run("pipeline not found", func(t *testing.T) {
		ci := newMockCIProvider()
		ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{}
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/999", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
		}
	})

	t.Run("invalid pipeline number", func(t *testing.T) {
		ci := newMockCIProvider()
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/notanumber", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})

	t.Run("invalid project id", func(t *testing.T) {
		ci := newMockCIProvider()
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/INVALID!/pipelines/1", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusBadRequest {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusBadRequest)
		}
	})
}

func TestInfrastructureHandler_GetPipelineSteps(t *testing.T) {
	t.Run("success", func(t *testing.T) {
		ci := newMockCIProvider()
		ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{
			{
				ID:     100,
				Number: 5,
				Status: "failure",
			},
		}

		router := setupInfraHandlerWithCI(ci)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/5/steps", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusOK {
			t.Errorf("status = %d, want %d, body: %s", rec.Code, http.StatusOK, rec.Body.String())
		}
	})

	t.Run("ci not configured", func(t *testing.T) {
		router := setupInfraHandlerWithCI(nil)
		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/1/steps", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusInternalServerError {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusInternalServerError)
		}
	})

	t.Run("pipeline not found", func(t *testing.T) {
		ci := newMockCIProvider()
		ci.pipelines["threesix/myapp"] = []*domain.CIPipeline{}
		router := setupInfraHandlerWithCI(ci)

		req := httptest.NewRequest("GET", "/projects/myapp/pipelines/999/steps", nil)
		rec := httptest.NewRecorder()
		router.ServeHTTP(rec, req)

		if rec.Code != http.StatusNotFound {
			t.Errorf("status = %d, want %d", rec.Code, http.StatusNotFound)
		}
	})
}

func TestFormatTime(t *testing.T) {
	t.Run("non-zero time", func(t *testing.T) {
		ts := time.Date(2025, 1, 15, 10, 30, 0, 0, time.UTC)
		got := formatTime(ts)
		want := "2025-01-15T10:30:00Z"
		if got != want {
			t.Errorf("formatTime() = %q, want %q", got, want)
		}
	})

	t.Run("zero time", func(t *testing.T) {
		got := formatTime(time.Time{})
		if got != "" {
			t.Errorf("formatTime(zero) = %q, want empty string", got)
		}
	})
}
