//go:build test && dev

package payments

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

	"crussell/db"
	"crussell/mw"
	"crussell/testutils"
	"crussell/testutils/fixtures"
	"crussell/testutils/jwt"

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

func TestCreateTillSale_OnTheHouse(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	reqBody := TillSaleRequest{
		ItemType:      "gift_card",
		Action:        "create",
		Amount:        50.00,
		PaymentMethod: "on_the_house",
	}

	bodyBytes, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req.Header.Set("Authorization", "Bearer "+adminToken)
	req.Header.Set("Content-Type", "application/json")

	req = req.WithContext(db.ContextWithTx(req.Context(), tx.(pgx.Tx)))

	w := httptest.NewRecorder()

	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp TillSaleResponse
	if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if resp.Status != "completed" {
		t.Errorf("expected status 'completed', got '%s'", resp.Status)
	}
	if resp.PaymentMethod != "on_the_house" {
		t.Errorf("expected payment method 'on_the_house', got '%s'", resp.PaymentMethod)
	}
	if resp.TotalAmount != 50.00 {
		t.Errorf("expected total amount 50.00, got %.2f", resp.TotalAmount)
	}
	if resp.ItemType != "gift_card" {
		t.Errorf("expected item type 'gift_card', got '%s'", resp.ItemType)
	}
	if resp.ItemID == nil || *resp.ItemID == "" {
		t.Error("expected item_id to be set (gift card ID)")
	}

	// Verify till_sale was created in DB
	var saleCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM till_sales WHERE id = $1", resp.ID).Scan(&saleCount)
	if err != nil {
		t.Errorf("failed to query till_sales: %v", err)
	}
	if saleCount != 1 {
		t.Errorf("expected 1 till_sale, got %d", saleCount)
	}

	// Verify gift card was created
	var gcCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM gift_cards WHERE id = $1", *resp.ItemID).Scan(&gcCount)
	if err != nil {
		t.Errorf("failed to query gift_cards: %v", err)
	}
	if gcCount != 1 {
		t.Errorf("expected 1 gift_card, got %d", gcCount)
	}
}

func TestCreateTillSale_Idempotency(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	idempotencyKey := "test-idempotency-key-001"

	reqBody := TillSaleRequest{
		ItemType:       "gift_card",
		Action:         "create",
		Amount:         25.00,
		PaymentMethod:  "on_the_house",
		IdempotencyKey: idempotencyKey,
	}

	bodyBytes, _ := json.Marshal(reqBody)

	// First request
	req1 := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req1.Header.Set("Authorization", "Bearer "+adminToken)
	req1.Header.Set("Content-Type", "application/json")

	req1 = req1.WithContext(db.ContextWithTx(req1.Context(), tx.(pgx.Tx)))

	w1 := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w1, req1)

	if w1.Code != http.StatusCreated {
		t.Errorf("first request: expected status 201, got %d. body: %s", w1.Code, w1.Body.String())
	}

	var resp1 TillSaleResponse
	if err := json.NewDecoder(w1.Body).Decode(&resp1); err != nil {
		t.Fatalf("failed to decode first response: %v", err)
	}

	if resp1.ID == "" {
		t.Fatal("expected first till_sale ID to be set")
	}

	// Second request with same idempotency key
	req2 := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req2.Header.Set("Authorization", "Bearer "+adminToken)
	req2.Header.Set("Content-Type", "application/json")

	req2 = req2.WithContext(db.ContextWithTx(req2.Context(), tx.(pgx.Tx)))

	w2 := httptest.NewRecorder()
	r2 := chi.NewRouter()
	r2.Use(mw.RequireAuth)
	r2.Post("/api/admin/till/sale", CreateTillSale)
	r2.ServeHTTP(w2, req2)

	// Idempotent response returns 200 (the handler does not set WriteHeader in the idempotency path)
	if w2.Code != http.StatusOK && w2.Code != http.StatusCreated {
		t.Errorf("second request: expected status 200 or 201, got %d. body: %s", w2.Code, w2.Body.String())
	}

	var resp2 TillSaleResponse
	if err := json.NewDecoder(w2.Body).Decode(&resp2); err != nil {
		t.Fatalf("failed to decode second response: %v", err)
	}

	if resp1.ID != resp2.ID {
		t.Errorf("expected same till_sale ID for idempotent request, got %s and %s", resp1.ID, resp2.ID)
	}

	// Verify only one till_sale exists
	var saleCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM till_sales").Scan(&saleCount)
	if err != nil {
		t.Errorf("failed to query till_sales: %v", err)
	}
	if saleCount != 1 {
		t.Errorf("expected 1 till_sale (idempotent), got %d", saleCount)
	}
}

func TestCreateTillSale_CreatesGiftCardTransaction(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	reqBody := TillSaleRequest{
		ItemType:      "gift_card",
		Action:        "create",
		Amount:        50.00,
		PaymentMethod: "on_the_house",
	}

	bodyBytes, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req.Header.Set("Authorization", "Bearer "+adminToken)
	req.Header.Set("Content-Type", "application/json")

	req = req.WithContext(db.ContextWithTx(req.Context(), tx.(pgx.Tx)))

	w := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w, req)

	if w.Code != http.StatusCreated {
		t.Errorf("expected status 201, got %d. body: %s", w.Code, w.Body.String())
	}

	var resp TillSaleResponse
	if err := json.NewDecoder(w.Body).Decode(&resp); err != nil {
		t.Fatalf("failed to decode response: %v", err)
	}

	if resp.ItemID == nil || *resp.ItemID == "" {
		t.Fatal("expected item_id to be set")
	}

	// Verify gift_card_transactions was created
	var txCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM gift_card_transactions WHERE gift_card_id = $1 AND transaction_type = 'purchase'", *resp.ItemID).Scan(&txCount)
	if err != nil {
		t.Errorf("failed to query gift_card_transactions: %v", err)
	}
	if txCount != 1 {
		t.Errorf("expected 1 gift_card_transaction with type 'purchase', got %d", txCount)
	}

	// Verify the transaction has reference_type = 'till_sale' and reference_id is set
	var refCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM gift_card_transactions WHERE gift_card_id = $1 AND reference_type = 'till_sale' AND reference_id IS NOT NULL", *resp.ItemID).Scan(&refCount)
	if err != nil {
		t.Errorf("failed to query gift_card_transactions with reference: %v", err)
	}
	if refCount != 1 {
		t.Errorf("expected 1 gift_card_transaction with reference_type 'till_sale' and reference_id set, got %d", refCount)
	}
}

func TestCreateTillSale_InvalidPaymentMethod(t *testing.T) {
	t.Parallel()
	_, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	reqBody := TillSaleRequest{
		ItemType:      "gift_card",
		Action:        "create",
		Amount:        50.00,
		PaymentMethod: "invalid_method",
	}

	bodyBytes, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req.Header.Set("Authorization", "Bearer "+adminToken)
	req.Header.Set("Content-Type", "application/json")

	req = req.WithContext(db.ContextWithTx(req.Context(), tx.(pgx.Tx)))

	w := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

func TestCreateTillSale_TopupOnRedeemedCard(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	// Create a test user to act as the redeemer
	redeemerID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create redeemer user: %v", err)
	}

	// Insert a gift card that is already redeemed
	var cardID string
	err = tx.QueryRow(ctx, `
		INSERT INTO gift_cards (total_funds_added, amount_remaining, created_by, redeemed_by, redeemed_at)
		VALUES (50.00, 0.00, $1, $2, NOW())
		RETURNING id
	`, adminID, redeemerID).Scan(&cardID)
	if err != nil {
		t.Fatalf("failed to insert redeemed gift card: %v", err)
	}

	// Try to topup the redeemed card
	reqBody := TillSaleRequest{
		ItemType:      "gift_card",
		Action:        "topup",
		Amount:        25.00,
		PaymentMethod: "on_the_house",
		GiftCardID:    &cardID,
	}

	bodyBytes, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req.Header.Set("Authorization", "Bearer "+adminToken)
	req.Header.Set("Content-Type", "application/json")

	req = req.WithContext(db.ContextWithTx(req.Context(), tx.(pgx.Tx)))

	w := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w, req)

	if w.Code != http.StatusBadRequest {
		t.Errorf("expected status 400, got %d. body: %s", w.Code, w.Body.String())
	}
}

func TestCreateTillSale_SavedCard_TransactionFailure_SkipsSquare(t *testing.T) {
	t.Parallel()
	ctx, tx := testutils.SetupTestTx(t)

	adminID, err := fixtures.CreateTestAdminUser(tx)
	if err != nil {
		t.Fatalf("failed to create admin user: %v", err)
	}

	userID, err := fixtures.CreateTestUser(tx)
	if err != nil {
		t.Fatalf("failed to create user: %v", err)
	}

	adminToken := jwt.GenerateTestToken(adminID, "admin")

	cardID, err := fixtures.CreateTestPaymentMethod(tx, userID, "sq_test_card_id", "VISA", "1234")
	if err != nil {
		t.Fatalf("failed to create saved card: %v", err)
	}

	cancelCtx, cancel := context.WithCancel(context.Background())
	cancel()

	reqBody := TillSaleRequest{
		ItemType:        "gift_card",
		Action:          "create",
		Amount:          50.00,
		PaymentMethod:   "saved_card",
		UserSavedCardID: &cardID,
		UserID:          &userID,
	}

	bodyBytes, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/api/admin/till/sale", bytes.NewReader(bodyBytes))
	req.Header.Set("Authorization", "Bearer "+adminToken)
	req.Header.Set("Content-Type", "application/json")

	req = req.WithContext(db.ContextWithTx(cancelCtx, tx.(pgx.Tx)))

	w := httptest.NewRecorder()
	r := chi.NewRouter()
	r.Use(mw.RequireAuth)
	r.Post("/api/admin/till/sale", CreateTillSale)
	r.ServeHTTP(w, req)

	if w.Code != http.StatusInternalServerError {
		t.Errorf("expected status 500, got %d. body: %s", w.Code, w.Body.String())
	}

	var completedCount int
	err = tx.QueryRow(ctx, "SELECT COUNT(*) FROM till_sales WHERE status = 'completed'").Scan(&completedCount)
	if err != nil {
		t.Errorf("failed to query till_sales: %v", err)
	}
	if completedCount != 0 {
		t.Errorf("expected 0 completed till_sales, got %d", completedCount)
	}
}
