package handlers

import (
	"bytes"
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"
	"time"

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

// mockWebhookRepository implements port.WebhookRepository for testing.
type mockWebhookRepository struct {
	webhooks   []*domain.Webhook
	deliveries []*domain.WebhookDelivery
	err        error
}

func (m *mockWebhookRepository) Create(ctx context.Context, webhook *domain.Webhook) error {
	if m.err != nil {
		return m.err
	}
	webhook.CreatedAt = time.Now()
	webhook.UpdatedAt = time.Now()
	m.webhooks = append(m.webhooks, webhook)
	return nil
}

func (m *mockWebhookRepository) GetByID(ctx context.Context, id domain.WebhookID) (*domain.Webhook, error) {
	if m.err != nil {
		return nil, m.err
	}
	for _, w := range m.webhooks {
		if w.ID == id {
			return w, nil
		}
	}
	return nil, domain.ErrWebhookNotFound
}

func (m *mockWebhookRepository) ListByProject(ctx context.Context, projectID string) ([]*domain.Webhook, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []*domain.Webhook
	for _, w := range m.webhooks {
		if w.ProjectID == projectID {
			result = append(result, w)
		}
	}
	return result, nil
}

func (m *mockWebhookRepository) ListEnabledByProjectAndEvent(ctx context.Context, projectID string, eventType domain.WebhookEventType) ([]*domain.Webhook, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []*domain.Webhook
	for _, w := range m.webhooks {
		if w.ProjectID == projectID && w.Enabled {
			for _, e := range w.Events {
				if e == eventType {
					result = append(result, w)
					break
				}
			}
		}
	}
	return result, nil
}

func (m *mockWebhookRepository) Update(ctx context.Context, webhook *domain.Webhook) error {
	if m.err != nil {
		return m.err
	}
	for i, w := range m.webhooks {
		if w.ID == webhook.ID {
			m.webhooks[i] = webhook
			return nil
		}
	}
	return domain.ErrWebhookNotFound
}

func (m *mockWebhookRepository) Delete(ctx context.Context, id domain.WebhookID) error {
	if m.err != nil {
		return m.err
	}
	for i, w := range m.webhooks {
		if w.ID == id {
			m.webhooks = append(m.webhooks[:i], m.webhooks[i+1:]...)
			return nil
		}
	}
	return domain.ErrWebhookNotFound
}

func (m *mockWebhookRepository) RecordDelivery(ctx context.Context, delivery *domain.WebhookDelivery) error {
	if m.err != nil {
		return m.err
	}
	m.deliveries = append(m.deliveries, delivery)
	return nil
}

func (m *mockWebhookRepository) GetDeliveries(ctx context.Context, webhookID domain.WebhookID, filters *domain.WebhookDeliveryFilters) ([]*domain.WebhookDelivery, error) {
	if m.err != nil {
		return nil, m.err
	}
	var result []*domain.WebhookDelivery
	for _, d := range m.deliveries {
		if d.WebhookID == webhookID {
			result = append(result, d)
		}
	}
	return result, nil
}

func (m *mockWebhookRepository) CleanupOldDeliveries(ctx context.Context, olderThanDays int) (int64, error) {
	return 0, m.err
}

func TestWebhookHandler_Create(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	tests := []struct {
		name       string
		projectID  string
		body       CreateWebhookRequest
		wantStatus int
	}{
		{
			name:      "valid webhook",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				URL:    "https://example.com/webhook",
				Events: []string{"command.started", "command.completed"},
			},
			wantStatus: http.StatusCreated,
		},
		{
			name:      "with custom secret",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				URL:    "https://example.com/webhook",
				Events: []string{"command.started"},
				Secret: "my-secret-key",
			},
			wantStatus: http.StatusCreated,
		},
		{
			name:      "missing url",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				Events: []string{"command.started"},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:      "invalid url",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				URL:    "not-a-valid-url",
				Events: []string{"command.started"},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:      "missing events",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				URL:    "https://example.com/webhook",
				Events: []string{},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:      "invalid event type",
			projectID: "proj-1",
			body: CreateWebhookRequest{
				URL:    "https://example.com/webhook",
				Events: []string{"invalid.event"},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:      "project not found",
			projectID: "unknown",
			body: CreateWebhookRequest{
				URL:    "https://example.com/webhook",
				Events: []string{"command.started"},
			},
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			webhookRepo := &mockWebhookRepository{}
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Post("/projects/{id}/webhooks/", h.Create)

			body, _ := json.Marshal(tt.body)
			req := httptest.NewRequest(http.MethodPost, "/projects/"+tt.projectID+"/webhooks/", bytes.NewReader(body))
			req.Header.Set("Content-Type", "application/json")
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("Create() status = %d, want %d, body: %s", w.Code, tt.wantStatus, w.Body.String())
			}

			if tt.wantStatus == http.StatusCreated {
				var resp struct {
					Data CreateWebhookResponse `json:"data"`
				}
				if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
					t.Fatalf("failed to unmarshal response: %v", err)
				}
				if resp.Data.Secret == "" {
					t.Error("Secret should be returned on creation")
				}
			}
		})
	}
}

func TestWebhookHandler_List(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	webhookRepo := &mockWebhookRepository{
		webhooks: []*domain.Webhook{
			{
				ID:        "wh-1",
				ProjectID: "proj-1",
				URL:       "https://example.com/webhook1",
				Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
				Enabled:   true,
				CreatedAt: time.Now(),
				UpdatedAt: time.Now(),
			},
			{
				ID:        "wh-2",
				ProjectID: "proj-1",
				URL:       "https://example.com/webhook2",
				Events:    []domain.WebhookEventType{domain.WebhookEventCommandCompleted},
				Enabled:   false,
				CreatedAt: time.Now(),
				UpdatedAt: time.Now(),
			},
		},
	}

	tests := []struct {
		name       string
		projectID  string
		wantStatus int
		wantCount  int
	}{
		{
			name:       "list webhooks",
			projectID:  "proj-1",
			wantStatus: http.StatusOK,
			wantCount:  2,
		},
		{
			name:       "project not found",
			projectID:  "unknown",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Get("/projects/{id}/webhooks/", h.List)

			req := httptest.NewRequest(http.MethodGet, "/projects/"+tt.projectID+"/webhooks/", nil)
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("List() status = %d, want %d", w.Code, tt.wantStatus)
			}

			if tt.wantStatus == http.StatusOK {
				var resp struct {
					Data struct {
						Webhooks []*WebhookDTO `json:"webhooks"`
						Total    int           `json:"total"`
					} `json:"data"`
				}
				if err := json.Unmarshal(w.Body.Bytes(), &resp); err != nil {
					t.Fatalf("failed to unmarshal response: %v", err)
				}
				if resp.Data.Total != tt.wantCount {
					t.Errorf("List() count = %d, want %d", resp.Data.Total, tt.wantCount)
				}
			}
		})
	}
}

func TestWebhookHandler_Get(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	webhookRepo := &mockWebhookRepository{
		webhooks: []*domain.Webhook{
			{
				ID:        "wh-123",
				ProjectID: "proj-1",
				URL:       "https://example.com/webhook",
				Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
				Enabled:   true,
				CreatedAt: time.Now(),
				UpdatedAt: time.Now(),
			},
		},
	}

	tests := []struct {
		name       string
		projectID  string
		webhookID  string
		wantStatus int
	}{
		{
			name:       "existing webhook",
			projectID:  "proj-1",
			webhookID:  "wh-123",
			wantStatus: http.StatusOK,
		},
		{
			name:       "webhook not found",
			projectID:  "proj-1",
			webhookID:  "wh-unknown",
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "project not found",
			projectID:  "unknown",
			webhookID:  "wh-123",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Get("/projects/{id}/webhooks/{webhookId}", h.Get)

			req := httptest.NewRequest(http.MethodGet, "/projects/"+tt.projectID+"/webhooks/"+tt.webhookID, nil)
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("Get() status = %d, want %d", w.Code, tt.wantStatus)
			}
		})
	}
}

func TestWebhookHandler_Update(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	tests := []struct {
		name       string
		projectID  string
		webhookID  string
		body       UpdateWebhookRequest
		wantStatus int
	}{
		{
			name:      "update url",
			projectID: "proj-1",
			webhookID: "wh-123",
			body: UpdateWebhookRequest{
				URL: "https://new-url.com/webhook",
			},
			wantStatus: http.StatusOK,
		},
		{
			name:      "disable webhook",
			projectID: "proj-1",
			webhookID: "wh-123",
			body: UpdateWebhookRequest{
				Enabled: boolPtr(false),
			},
			wantStatus: http.StatusOK,
		},
		{
			name:      "invalid url",
			projectID: "proj-1",
			webhookID: "wh-123",
			body: UpdateWebhookRequest{
				URL: "not-a-url",
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name:      "webhook not found",
			projectID: "proj-1",
			webhookID: "wh-unknown",
			body: UpdateWebhookRequest{
				URL: "https://example.com",
			},
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			webhookRepo := &mockWebhookRepository{
				webhooks: []*domain.Webhook{
					{
						ID:        "wh-123",
						ProjectID: "proj-1",
						URL:       "https://example.com/webhook",
						Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
						Enabled:   true,
						CreatedAt: time.Now(),
						UpdatedAt: time.Now(),
					},
				},
			}
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Put("/projects/{id}/webhooks/{webhookId}", h.Update)

			body, _ := json.Marshal(tt.body)
			req := httptest.NewRequest(http.MethodPut, "/projects/"+tt.projectID+"/webhooks/"+tt.webhookID, bytes.NewReader(body))
			req.Header.Set("Content-Type", "application/json")
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("Update() status = %d, want %d, body: %s", w.Code, tt.wantStatus, w.Body.String())
			}
		})
	}
}

func TestWebhookHandler_Delete(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	tests := []struct {
		name       string
		projectID  string
		webhookID  string
		wantStatus int
	}{
		{
			name:       "delete existing webhook",
			projectID:  "proj-1",
			webhookID:  "wh-123",
			wantStatus: http.StatusOK,
		},
		{
			name:       "webhook not found",
			projectID:  "proj-1",
			webhookID:  "wh-unknown",
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "project not found",
			projectID:  "unknown",
			webhookID:  "wh-123",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			webhookRepo := &mockWebhookRepository{
				webhooks: []*domain.Webhook{
					{
						ID:        "wh-123",
						ProjectID: "proj-1",
						URL:       "https://example.com/webhook",
						Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
						Enabled:   true,
						CreatedAt: time.Now(),
						UpdatedAt: time.Now(),
					},
				},
			}
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Delete("/projects/{id}/webhooks/{webhookId}", h.Delete)

			req := httptest.NewRequest(http.MethodDelete, "/projects/"+tt.projectID+"/webhooks/"+tt.webhookID, nil)
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("Delete() status = %d, want %d", w.Code, tt.wantStatus)
			}
		})
	}
}

func TestWebhookHandler_GetDeliveries(t *testing.T) {
	projectRepo := newMockProjectRepo()
	projectRepo.Register(context.Background(), &domain.Project{ID: "proj-1", Name: "Test Project"})

	webhookRepo := &mockWebhookRepository{
		webhooks: []*domain.Webhook{
			{
				ID:        "wh-123",
				ProjectID: "proj-1",
				URL:       "https://example.com/webhook",
				Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
				Enabled:   true,
				CreatedAt: time.Now(),
				UpdatedAt: time.Now(),
			},
		},
		deliveries: []*domain.WebhookDelivery{
			{
				ID:             "del-1",
				WebhookID:      "wh-123",
				EventType:      domain.WebhookEventCommandStarted,
				Payload:        `{"test": true}`,
				ResponseStatus: 200,
				Success:        true,
				DeliveredAt:    time.Now(),
			},
		},
	}

	tests := []struct {
		name       string
		projectID  string
		webhookID  string
		query      string
		wantStatus int
	}{
		{
			name:       "get deliveries",
			projectID:  "proj-1",
			webhookID:  "wh-123",
			query:      "",
			wantStatus: http.StatusOK,
		},
		{
			name:       "with filters",
			projectID:  "proj-1",
			webhookID:  "wh-123",
			query:      "?success=true&limit=10",
			wantStatus: http.StatusOK,
		},
		{
			name:       "webhook not found",
			projectID:  "proj-1",
			webhookID:  "wh-unknown",
			wantStatus: http.StatusNotFound,
		},
		{
			name:       "project not found",
			projectID:  "unknown",
			webhookID:  "wh-123",
			wantStatus: http.StatusNotFound,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			h := NewWebhookHandler(webhookRepo, projectRepo)

			r := chi.NewRouter()
			r.Get("/projects/{id}/webhooks/{webhookId}/deliveries", h.GetDeliveries)

			req := httptest.NewRequest(http.MethodGet, "/projects/"+tt.projectID+"/webhooks/"+tt.webhookID+"/deliveries"+tt.query, nil)
			w := httptest.NewRecorder()

			r.ServeHTTP(w, req)

			if w.Code != tt.wantStatus {
				t.Errorf("GetDeliveries() status = %d, want %d", w.Code, tt.wantStatus)
			}
		})
	}
}

func boolPtr(b bool) *bool {
	return &b
}
