package postgres

import (
	"context"
	"database/sql"
	"testing"
	"time"

	"github.com/orchard9/rdev/internal/domain"
	"github.com/orchard9/rdev/internal/testutil"
)

func cleanupTestWebhooks(t *testing.T, db *sql.DB) {
	t.Helper()
	// Clean deliveries first due to foreign key constraint
	_, err := db.Exec("DELETE FROM webhook_deliveries WHERE webhook_id IN (SELECT id FROM webhooks WHERE project_id LIKE 'test-%')")
	if err != nil {
		t.Logf("cleanup test webhook deliveries: %v", err)
	}
	_, err = db.Exec("DELETE FROM webhooks WHERE project_id LIKE 'test-%'")
	if err != nil {
		t.Logf("cleanup test webhooks: %v", err)
	}
}

func TestWebhookRepository_Create(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	t.Run("creates webhook successfully", func(t *testing.T) {
		webhook := &domain.Webhook{
			ID:        "wh-test-create-1",
			ProjectID: "test-proj-webhook-1",
			URL:       "https://example.com/webhook",
			Secret:    "test-secret-123",
			Events: []domain.WebhookEventType{
				domain.WebhookEventCommandStarted,
				domain.WebhookEventCommandCompleted,
			},
			Enabled: true,
		}

		err := repo.Create(ctx, webhook)
		if err != nil {
			t.Fatalf("Create() error = %v", err)
		}

		if webhook.CreatedAt.IsZero() {
			t.Error("CreatedAt should be set after create")
		}
		if webhook.UpdatedAt.IsZero() {
			t.Error("UpdatedAt should be set after create")
		}
	})

	t.Run("creates webhook without secret", func(t *testing.T) {
		webhook := &domain.Webhook{
			ID:        "wh-test-create-nosecret",
			ProjectID: "test-proj-webhook-2",
			URL:       "https://example.com/webhook2",
			Secret:    "",
			Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
			Enabled:   true,
		}

		err := repo.Create(ctx, webhook)
		if err != nil {
			t.Fatalf("Create() error = %v", err)
		}

		// Retrieve and verify secret is empty
		retrieved, _ := repo.GetByID(ctx, "wh-test-create-nosecret")
		if retrieved.Secret != "" {
			t.Errorf("Secret = %q, want empty", retrieved.Secret)
		}
	})
}

func TestWebhookRepository_Update(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	t.Run("updates existing webhook", func(t *testing.T) {
		// Create webhook first
		webhook := &domain.Webhook{
			ID:        "wh-test-update-1",
			ProjectID: "test-proj-update",
			URL:       "https://example.com/original",
			Secret:    "original-secret",
			Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
			Enabled:   true,
		}
		repo.Create(ctx, webhook)

		// Update it
		webhook.URL = "https://example.com/updated"
		webhook.Secret = "updated-secret"
		webhook.Enabled = false
		webhook.Events = []domain.WebhookEventType{
			domain.WebhookEventCommandCompleted,
			domain.WebhookEventCommandFailed,
		}

		originalUpdatedAt := webhook.UpdatedAt
		time.Sleep(10 * time.Millisecond) // Ensure timestamp changes

		err := repo.Update(ctx, webhook)
		if err != nil {
			t.Fatalf("Update() error = %v", err)
		}

		if !webhook.UpdatedAt.After(originalUpdatedAt) {
			t.Error("UpdatedAt should be updated after Update()")
		}

		// Verify changes
		retrieved, _ := repo.GetByID(ctx, "wh-test-update-1")
		if retrieved.URL != "https://example.com/updated" {
			t.Errorf("URL = %q, want %q", retrieved.URL, "https://example.com/updated")
		}
		if retrieved.Secret != "updated-secret" {
			t.Errorf("Secret = %q, want %q", retrieved.Secret, "updated-secret")
		}
		if retrieved.Enabled {
			t.Error("Enabled should be false after update")
		}
		if len(retrieved.Events) != 2 {
			t.Errorf("Events length = %d, want 2", len(retrieved.Events))
		}
	})

	t.Run("returns error for non-existent webhook", func(t *testing.T) {
		webhook := &domain.Webhook{
			ID:        "wh-nonexistent",
			ProjectID: "test-proj",
			URL:       "https://example.com",
			Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		}

		err := repo.Update(ctx, webhook)
		if err != domain.ErrWebhookNotFound {
			t.Errorf("Update() error = %v, want %v", err, domain.ErrWebhookNotFound)
		}
	})
}

func TestWebhookRepository_Delete(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	t.Run("deletes existing webhook", func(t *testing.T) {
		webhook := &domain.Webhook{
			ID:        "wh-test-delete-1",
			ProjectID: "test-proj-delete",
			URL:       "https://example.com/delete",
			Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
			Enabled:   true,
		}
		repo.Create(ctx, webhook)

		err := repo.Delete(ctx, "wh-test-delete-1")
		if err != nil {
			t.Fatalf("Delete() error = %v", err)
		}

		// Verify deleted
		_, err = repo.GetByID(ctx, "wh-test-delete-1")
		if err != domain.ErrWebhookNotFound {
			t.Errorf("GetByID() after delete error = %v, want %v", err, domain.ErrWebhookNotFound)
		}
	})

	t.Run("returns error for non-existent webhook", func(t *testing.T) {
		err := repo.Delete(ctx, "wh-nonexistent")
		if err != domain.ErrWebhookNotFound {
			t.Errorf("Delete() error = %v, want %v", err, domain.ErrWebhookNotFound)
		}
	})
}

func TestWebhookRepository_GetByID(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	t.Run("gets existing webhook", func(t *testing.T) {
		webhook := &domain.Webhook{
			ID:        "wh-test-getbyid-1",
			ProjectID: "test-proj-getbyid",
			URL:       "https://example.com/getbyid",
			Secret:    "get-secret",
			Events: []domain.WebhookEventType{
				domain.WebhookEventCommandStarted,
				domain.WebhookEventCommandCompleted,
			},
			Enabled: true,
		}
		repo.Create(ctx, webhook)

		retrieved, err := repo.GetByID(ctx, "wh-test-getbyid-1")
		if err != nil {
			t.Fatalf("GetByID() error = %v", err)
		}

		if retrieved.URL != "https://example.com/getbyid" {
			t.Errorf("URL = %q, want %q", retrieved.URL, "https://example.com/getbyid")
		}
		if retrieved.Secret != "get-secret" {
			t.Errorf("Secret = %q, want %q", retrieved.Secret, "get-secret")
		}
		if len(retrieved.Events) != 2 {
			t.Errorf("Events length = %d, want 2", len(retrieved.Events))
		}
	})

	t.Run("returns error for non-existent webhook", func(t *testing.T) {
		_, err := repo.GetByID(ctx, "wh-nonexistent")
		if err != domain.ErrWebhookNotFound {
			t.Errorf("GetByID() error = %v, want %v", err, domain.ErrWebhookNotFound)
		}
	})
}

func TestWebhookRepository_ListByProject(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	projectID := "test-proj-list"

	// Create multiple webhooks
	for i := 0; i < 3; i++ {
		webhook := &domain.Webhook{
			ID:        domain.WebhookID("wh-test-list-" + string(rune('a'+i))),
			ProjectID: projectID,
			URL:       "https://example.com/list" + string(rune('a'+i)),
			Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
			Enabled:   true,
		}
		repo.Create(ctx, webhook)
		time.Sleep(10 * time.Millisecond) // Ensure different timestamps
	}

	// Create webhook in different project
	otherWebhook := &domain.Webhook{
		ID:        "wh-test-list-other",
		ProjectID: "test-proj-list-other",
		URL:       "https://example.com/other",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   true,
	}
	repo.Create(ctx, otherWebhook)

	t.Run("lists all webhooks for project", func(t *testing.T) {
		webhooks, err := repo.ListByProject(ctx, projectID)
		if err != nil {
			t.Fatalf("ListByProject() error = %v", err)
		}

		if len(webhooks) != 3 {
			t.Errorf("ListByProject() returned %d webhooks, want 3", len(webhooks))
		}

		// Verify all belong to the project
		for _, wh := range webhooks {
			if wh.ProjectID != projectID {
				t.Errorf("Webhook has ProjectID = %q, want %q", wh.ProjectID, projectID)
			}
		}
	})

	t.Run("returns empty slice for project with no webhooks", func(t *testing.T) {
		webhooks, err := repo.ListByProject(ctx, "test-proj-no-webhooks")
		if err != nil {
			t.Fatalf("ListByProject() error = %v", err)
		}

		if len(webhooks) != 0 {
			t.Errorf("ListByProject() returned %d webhooks, want 0", len(webhooks))
		}
	})
}

func TestWebhookRepository_ListEnabledByProjectAndEvent(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	projectID := "test-proj-enabled"

	// Create webhooks with different configurations
	enabledStarted := &domain.Webhook{
		ID:        "wh-enabled-started",
		ProjectID: projectID,
		URL:       "https://example.com/enabled-started",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   true,
	}
	repo.Create(ctx, enabledStarted)

	enabledCompleted := &domain.Webhook{
		ID:        "wh-enabled-completed",
		ProjectID: projectID,
		URL:       "https://example.com/enabled-completed",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandCompleted},
		Enabled:   true,
	}
	repo.Create(ctx, enabledCompleted)

	disabledStarted := &domain.Webhook{
		ID:        "wh-disabled-started",
		ProjectID: projectID,
		URL:       "https://example.com/disabled-started",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   false,
	}
	repo.Create(ctx, disabledStarted)

	t.Run("returns only enabled webhooks with matching event", func(t *testing.T) {
		webhooks, err := repo.ListEnabledByProjectAndEvent(ctx, projectID, domain.WebhookEventCommandStarted)
		if err != nil {
			t.Fatalf("ListEnabledByProjectAndEvent() error = %v", err)
		}

		if len(webhooks) != 1 {
			t.Errorf("ListEnabledByProjectAndEvent() returned %d webhooks, want 1", len(webhooks))
		}

		if len(webhooks) > 0 && webhooks[0].ID != "wh-enabled-started" {
			t.Errorf("Webhook ID = %q, want %q", webhooks[0].ID, "wh-enabled-started")
		}
	})

	t.Run("returns empty when no matching webhooks", func(t *testing.T) {
		webhooks, err := repo.ListEnabledByProjectAndEvent(ctx, projectID, domain.WebhookEventCommandFailed)
		if err != nil {
			t.Fatalf("ListEnabledByProjectAndEvent() error = %v", err)
		}

		if len(webhooks) != 0 {
			t.Errorf("ListEnabledByProjectAndEvent() returned %d webhooks, want 0", len(webhooks))
		}
	})
}

func TestWebhookRepository_RecordDelivery(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	// Create webhook first
	webhook := &domain.Webhook{
		ID:        "wh-test-delivery",
		ProjectID: "test-proj-delivery",
		URL:       "https://example.com/delivery",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   true,
	}
	repo.Create(ctx, webhook)

	t.Run("records successful delivery", func(t *testing.T) {
		delivery := &domain.WebhookDelivery{
			ID:             "del-test-success",
			WebhookID:      "wh-test-delivery",
			EventType:      domain.WebhookEventCommandStarted,
			Payload:        `{"event":"command.started"}`,
			ResponseStatus: 200,
			ResponseBody:   "OK",
			DeliveredAt:    time.Now(),
			Success:        true,
			RetryCount:     0,
		}

		err := repo.RecordDelivery(ctx, delivery)
		if err != nil {
			t.Fatalf("RecordDelivery() error = %v", err)
		}
	})

	t.Run("records failed delivery", func(t *testing.T) {
		delivery := &domain.WebhookDelivery{
			ID:             "del-test-failure",
			WebhookID:      "wh-test-delivery",
			EventType:      domain.WebhookEventCommandStarted,
			Payload:        `{"event":"command.started"}`,
			ResponseStatus: 500,
			ResponseBody:   "Internal Server Error",
			DeliveredAt:    time.Now(),
			Success:        false,
			RetryCount:     3,
			ErrorMessage:   "server returned 500",
		}

		err := repo.RecordDelivery(ctx, delivery)
		if err != nil {
			t.Fatalf("RecordDelivery() error = %v", err)
		}
	})
}

func TestWebhookRepository_GetDeliveries(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	// Create webhook
	webhook := &domain.Webhook{
		ID:        "wh-test-get-deliveries",
		ProjectID: "test-proj-get-deliveries",
		URL:       "https://example.com/deliveries",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   true,
	}
	repo.Create(ctx, webhook)

	// Create deliveries
	for i := 0; i < 5; i++ {
		delivery := &domain.WebhookDelivery{
			ID:             domain.WebhookDeliveryID("del-get-" + string(rune('a'+i))),
			WebhookID:      "wh-test-get-deliveries",
			EventType:      domain.WebhookEventCommandStarted,
			Payload:        `{"event":"command.started"}`,
			ResponseStatus: 200,
			DeliveredAt:    time.Now(),
			Success:        i%2 == 0, // Alternate success/failure
		}
		_ = repo.RecordDelivery(ctx, delivery)
		time.Sleep(10 * time.Millisecond)
	}

	t.Run("gets all deliveries", func(t *testing.T) {
		deliveries, err := repo.GetDeliveries(ctx, "wh-test-get-deliveries", nil)
		if err != nil {
			t.Fatalf("GetDeliveries() error = %v", err)
		}

		if len(deliveries) != 5 {
			t.Errorf("GetDeliveries() returned %d deliveries, want 5", len(deliveries))
		}
	})

	t.Run("filters by success", func(t *testing.T) {
		success := true
		deliveries, err := repo.GetDeliveries(ctx, "wh-test-get-deliveries", &domain.WebhookDeliveryFilters{
			Success: &success,
			Limit:   100,
		})
		if err != nil {
			t.Fatalf("GetDeliveries() error = %v", err)
		}

		for _, d := range deliveries {
			if !d.Success {
				t.Error("GetDeliveries() returned unsuccessful delivery when filtering by success=true")
			}
		}
	})

	t.Run("applies limit", func(t *testing.T) {
		deliveries, err := repo.GetDeliveries(ctx, "wh-test-get-deliveries", &domain.WebhookDeliveryFilters{
			Limit: 2,
		})
		if err != nil {
			t.Fatalf("GetDeliveries() error = %v", err)
		}

		if len(deliveries) != 2 {
			t.Errorf("GetDeliveries() returned %d deliveries, want 2", len(deliveries))
		}
	})
}

func TestWebhookRepository_CleanupOldDeliveries(t *testing.T) {
	db := testutil.TestDB(t)
	t.Cleanup(func() { cleanupTestWebhooks(t, db) })

	repo := NewWebhookRepository(db)
	ctx := context.Background()

	// Create webhook
	webhook := &domain.Webhook{
		ID:        "wh-test-cleanup",
		ProjectID: "test-proj-cleanup",
		URL:       "https://example.com/cleanup",
		Events:    []domain.WebhookEventType{domain.WebhookEventCommandStarted},
		Enabled:   true,
	}
	repo.Create(ctx, webhook)

	// Create deliveries
	for i := 0; i < 3; i++ {
		delivery := &domain.WebhookDelivery{
			ID:             domain.WebhookDeliveryID("del-cleanup-" + string(rune('a'+i))),
			WebhookID:      "wh-test-cleanup",
			EventType:      domain.WebhookEventCommandStarted,
			Payload:        `{}`,
			ResponseStatus: 200,
			DeliveredAt:    time.Now(),
			Success:        true,
		}
		_ = repo.RecordDelivery(ctx, delivery)
	}

	t.Run("cleanup runs without error", func(t *testing.T) {
		deleted, err := repo.CleanupOldDeliveries(ctx, 30)
		if err != nil {
			t.Fatalf("CleanupOldDeliveries() error = %v", err)
		}

		// Newly created deliveries shouldn't be deleted
		if deleted != 0 {
			t.Logf("CleanupOldDeliveries() deleted %d deliveries (expected 0 for new deliveries)", deleted)
		}
	})
}
