//go:build test

package admin

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

	"crussell/clock"
	"crussell/db"
	"crussell/mw"
	"crussell/testutils"
	"crussell/testutils/fixtures"

	"github.com/go-chi/chi/v5"
)

var testAdminID string

func makeCampaignRequest(handler http.Handler, method, path string, body interface{}, ctx context.Context, adminID string) *httptest.ResponseRecorder {
	var req *http.Request
	if body != nil {
		bodyBytes, _ := json.Marshal(body)
		req = httptest.NewRequest(method, path, strings.NewReader(string(bodyBytes)))
		req.Header.Set("Content-Type", "application/json")
	} else {
		req = httptest.NewRequest(method, path, nil)
	}

	rctx := chi.NewRouteContext()
	prefix := "/api/admin/discount-campaigns/"
	if strings.HasPrefix(path, prefix) {
		suffix := path[len(prefix):]
		if slashIdx := strings.Index(suffix, "/"); slashIdx >= 0 {
			rctx.URLParams.Add("id", suffix[:slashIdx])
		} else if suffix != "" {
			rctx.URLParams.Add("id", suffix)
		}
	}

	ctx = context.WithValue(ctx, chi.RouteCtxKey, rctx)
	ctx = context.WithValue(ctx, mw.UserIDKey, adminID)
	ctx = context.WithValue(ctx, mw.UserRoleKey, "admin")
	req = req.WithContext(ctx)

	w := httptest.NewRecorder()
	handler.ServeHTTP(w, req)
	return w
}

func insertTimeBasedCampaign(t *testing.T, ctx context.Context, tx db.Querier, adminID, name string, discount float64, status string) string {
	t.Helper()
	startDate := fmt.Sprintf("%sZ", clock.Now().Add(-1*time.Hour).Format("2006-01-02T15:04:05"))
	endDate := fmt.Sprintf("%sZ", clock.Now().Add(7*24*time.Hour).Format("2006-01-02T15:04:05"))
	var id string
	err := tx.QueryRow(ctx, `
		INSERT INTO discount_campaigns (name, campaign_type, discount_percent, status, start_date, end_date, created_by)
		VALUES ($1, 'time_based', $2, $3, $4::timestamptz, $5::timestamptz, $6)
		RETURNING id
	`, name, discount, status, startDate, endDate, adminID).Scan(&id)
	if err != nil {
		t.Fatalf("failed to insert time_based campaign: %v", err)
	}
	return id
}

func insertMilestoneCampaign(t *testing.T, ctx context.Context, tx db.Querier, adminID, name string, discount float64, status string) string {
	t.Helper()
	mt := "per_user_booking_count"
	mv := 5
	mu := "bookings"
	var id string
	err := tx.QueryRow(ctx, `
		INSERT INTO discount_campaigns (name, campaign_type, discount_percent, status,
			milestone_type, milestone_value, milestone_unit, created_by)
		VALUES ($1, 'milestone', $2, $3, $4, $5, $6, $7)
		RETURNING id
	`, name, discount, status, mt, mv, mu, adminID).Scan(&id)
	if err != nil {
		t.Fatalf("failed to insert milestone campaign: %v", err)
	}
	return id
}

// =============================================================================
// List Campaigns Tests
// =============================================================================

func TestGetDiscountCampaigns_Empty(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	handler := http.HandlerFunc(GetDiscountCampaigns)
	w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns", nil, ctx, adminID)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var campaigns []DiscountCampaign
	if err := json.Unmarshal(w.Body.Bytes(), &campaigns); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if len(campaigns) != 0 {
		t.Errorf("expected empty list, got %d items", len(campaigns))
	}
}

func TestGetDiscountCampaigns_WithCampaigns(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	insertTimeBasedCampaign(t, ctx, tx, adminID, "Summer Sale", 15, "active")

	handler := http.HandlerFunc(GetDiscountCampaigns)
	w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns", nil, ctx, adminID)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var campaigns []DiscountCampaign
	if err := json.Unmarshal(w.Body.Bytes(), &campaigns); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if len(campaigns) != 1 {
		t.Fatalf("expected 1 campaign, got %d", len(campaigns))
	}
	if campaigns[0].Name != "Summer Sale" {
		t.Errorf("expected 'Summer Sale', got %q", campaigns[0].Name)
	}
	if campaigns[0].DiscountPercent != 15 {
		t.Errorf("expected 15%% discount, got %.0f%%", campaigns[0].DiscountPercent)
	}
	if campaigns[0].CampaignType != "time_based" {
		t.Errorf("expected 'time_based', got %q", campaigns[0].CampaignType)
	}
	if campaigns[0].Status != "active" {
		t.Errorf("expected 'active', got %q", campaigns[0].Status)
	}
}

func TestGetDiscountCampaigns_FilterByStatus(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	insertMilestoneCampaign(t, ctx, tx, adminID, "Draft Campaign", 10, "draft")
	insertMilestoneCampaign(t, ctx, tx, adminID, "Active Campaign", 20, "active")
	insertMilestoneCampaign(t, ctx, tx, adminID, "Cancelled Campaign", 5, "cancelled")

	handler := http.HandlerFunc(GetDiscountCampaigns)

	t.Run("filter_by_active", func(t *testing.T) {
		w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns?status=active", nil, ctx, adminID)
		if w.Code != http.StatusOK {
			t.Fatalf("expected 200, got %d", w.Code)
		}
		var campaigns []DiscountCampaign
		json.Unmarshal(w.Body.Bytes(), &campaigns)
		if len(campaigns) != 1 {
			t.Errorf("expected 1 active campaign, got %d", len(campaigns))
		}
	})

	t.Run("filter_by_invalid_status", func(t *testing.T) {
		w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns?status=invalid", nil, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for invalid status filter, got %d", w.Code)
		}
	})
}

// =============================================================================
// Create Campaign Tests
// =============================================================================

func TestCreateDiscountCampaign_TimeBased(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	req := CreateCampaignRequest{
		Name:            "Summer Sale",
		CampaignType:    "time_based",
		DiscountPercent: 15,
		StartDate:       strPtr(fmt.Sprintf("%sZ", clock.Now().Format("2006-01-02T15:04:05"))),
		EndDate:         strPtr(fmt.Sprintf("%sZ", clock.Now().Add(7*24*time.Hour).Format("2006-01-02T15:04:05"))),
		MaxRedemptions:  intPtr(100),
	}

	handler := http.HandlerFunc(CreateDiscountCampaign)
	w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201, got %d. body: %s", w.Code, w.Body.String())
	}

	var campaign DiscountCampaign
	if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if campaign.Name != "Summer Sale" {
		t.Errorf("expected 'Summer Sale', got %q", campaign.Name)
	}
	if campaign.Status != "draft" {
		t.Errorf("expected initial status 'draft', got %q", campaign.Status)
	}
	if campaign.ID == "" {
		t.Error("expected campaign ID to be set")
	}
}

func TestCreateDiscountCampaign_Milestone(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	mt := "per_user_booking_count"
	mv := 5
	mu := "bookings"
	req := CreateCampaignRequest{
		Name:            "Loyalty Milestone",
		CampaignType:    "milestone",
		DiscountPercent: 25,
		MilestoneType:   &mt,
		MilestoneValue:  &mv,
		MilestoneUnit:   &mu,
	}

	handler := http.HandlerFunc(CreateDiscountCampaign)
	w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
	if w.Code != http.StatusCreated {
		t.Fatalf("expected 201, got %d. body: %s", w.Code, w.Body.String())
	}

	var campaign DiscountCampaign
	if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if campaign.CampaignType != "milestone" {
		t.Errorf("expected 'milestone', got %q", campaign.CampaignType)
	}
	if campaign.MilestoneValue == nil || *campaign.MilestoneValue != 5 {
		t.Errorf("expected milestone value 5, got %v", campaign.MilestoneValue)
	}
}

func TestCreateDiscountCampaign_ValidationErrors(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	handler := http.HandlerFunc(CreateDiscountCampaign)

	t.Run("empty_name", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "",
			CampaignType:    "time_based",
			DiscountPercent: 10,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for empty name, got %d", w.Code)
		}
	})

	t.Run("invalid_discount_percent_zero", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "time_based",
			DiscountPercent: 0,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for zero discount, got %d", w.Code)
		}
	})

	t.Run("invalid_discount_percent_over_100", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "time_based",
			DiscountPercent: 150,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for discount >100, got %d", w.Code)
		}
	})

	t.Run("invalid_campaign_type", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "invalid_type",
			DiscountPercent: 10,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for invalid type, got %d", w.Code)
		}
	})

	t.Run("time_based_missing_dates", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "time_based",
			DiscountPercent: 10,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for missing dates, got %d", w.Code)
		}
	})

	t.Run("time_based_end_before_start", func(t *testing.T) {
		future := clock.Now().Add(7 * 24 * time.Hour)
		past := clock.Now().Add(-7 * 24 * time.Hour)
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "time_based",
			DiscountPercent: 10,
			StartDate:       strPtr(fmt.Sprintf("%sZ", future.Format("2006-01-02T15:04:05"))),
			EndDate:         strPtr(fmt.Sprintf("%sZ", past.Format("2006-01-02T15:04:05"))),
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for end before start, got %d", w.Code)
		}
	})

	t.Run("milestone_missing_required_fields", func(t *testing.T) {
		req := CreateCampaignRequest{
			Name:            "Test",
			CampaignType:    "milestone",
			DiscountPercent: 10,
		}
		w := makeCampaignRequest(handler, "POST", "/api/admin/discount-campaigns", req, ctx, adminID)
		if w.Code != http.StatusBadRequest {
			t.Errorf("expected 400 for missing milestone fields, got %d", w.Code)
		}
	})
}

// =============================================================================
// Update Campaign Tests
// =============================================================================

func TestUpdateDiscountCampaign_UpdateName(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	campaignID := insertMilestoneCampaign(t, ctx, tx, adminID, "Old Name", 10, "draft")

	newName := "New Name"
	req := UpdateCampaignRequest{Name: &newName}
	handler := http.HandlerFunc(UpdateDiscountCampaign)
	w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/"+campaignID, req, ctx, adminID)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var campaign DiscountCampaign
	if err := json.Unmarshal(w.Body.Bytes(), &campaign); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if campaign.Name != "New Name" {
		t.Errorf("expected 'New Name', got %q", campaign.Name)
	}
}

func TestUpdateDiscountCampaign_NotFound(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	newName := "Test"
	req := UpdateCampaignRequest{Name: &newName}
	handler := http.HandlerFunc(UpdateDiscountCampaign)
	w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/nonexistent-id", req, ctx, adminID)
	if w.Code != http.StatusNotFound {
		t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
	}
}

func TestUpdateDiscountCampaign_InvalidStatus(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	campaignID := insertMilestoneCampaign(t, ctx, tx, adminID, "Test", 10, "draft")

	badStatus := "invalid_status"
	req := UpdateCampaignRequest{Status: &badStatus}
	handler := http.HandlerFunc(UpdateDiscountCampaign)
	w := makeCampaignRequest(handler, "PUT", "/api/admin/discount-campaigns/"+campaignID, req, ctx, adminID)
	if w.Code != http.StatusBadRequest {
		t.Errorf("expected 400 for invalid status, got %d", w.Code)
	}
}

// =============================================================================
// Delete Campaign Tests
// =============================================================================

func TestDeleteDiscountCampaign_HappyPath(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	campaignID := insertMilestoneCampaign(t, ctx, tx, adminID, "Test", 10, "active")

	handler := http.HandlerFunc(DeleteDiscountCampaign)
	w := makeCampaignRequest(handler, "DELETE", "/api/admin/discount-campaigns/"+campaignID, nil, ctx, adminID)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var status string
	err = tx.QueryRow(ctx,
		"SELECT status FROM discount_campaigns WHERE id = $1", campaignID).Scan(&status)
	if err != nil {
		t.Fatalf("failed to query campaign: %v", err)
	}
	if status != "cancelled" {
		t.Errorf("expected status 'cancelled', got %q", status)
	}
}

func TestDeleteDiscountCampaign_NotFound(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	handler := http.HandlerFunc(DeleteDiscountCampaign)
	w := makeCampaignRequest(handler, "DELETE", "/api/admin/discount-campaigns/nonexistent-id", nil, ctx, adminID)
	if w.Code != http.StatusNotFound {
		t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
	}
}

// =============================================================================
// Campaign Stats Tests
// =============================================================================

func TestGetCampaignStats_NoUsage(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	campaignID := insertMilestoneCampaign(t, ctx, tx, adminID, "Test", 10, "active")

	handler := http.HandlerFunc(GetCampaignStats)
	w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns/"+campaignID+"/stats", nil, ctx, adminID)
	if w.Code != http.StatusOK {
		t.Fatalf("expected 200, got %d. body: %s", w.Code, w.Body.String())
	}

	var stats CampaignStats
	if err := json.Unmarshal(w.Body.Bytes(), &stats); err != nil {
		t.Fatalf("failed to parse response: %v", err)
	}
	if stats.Campaign.ID != campaignID {
		t.Errorf("expected campaign ID %q, got %q", campaignID, stats.Campaign.ID)
	}
	if stats.TotalDiscounts != 0 {
		t.Errorf("expected 0 total discounts, got %.2f", stats.TotalDiscounts)
	}
	if stats.BookingCount != 0 {
		t.Errorf("expected 0 booking count, got %d", stats.BookingCount)
	}
}

func TestGetCampaignStats_NotFound(t *testing.T) {
	ctx, tx := testutils.SetupTestTx(t)
	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin: %v", err)
	}
	testAdminID = adminID

	handler := http.HandlerFunc(GetCampaignStats)
	w := makeCampaignRequest(handler, "GET", "/api/admin/discount-campaigns/nonexistent-id/stats", nil, ctx, adminID)
	if w.Code != http.StatusNotFound {
		t.Errorf("expected 404 for nonexistent campaign, got %d", w.Code)
	}
}
