package handlers

import (
	"bytes"
	"context"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strconv"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
)

// mockPaymentCredentialsService mocks the credentials service
type mockPaymentCredentialsService struct {
	getDecryptedKeyFunc func(ctx context.Context, keyName string) (string, error)
}

func (m *mockPaymentCredentialsService) GetDecryptedKey(ctx context.Context, keyName string) (string, error) {
	if m.getDecryptedKeyFunc != nil {
		return m.getDecryptedKeyFunc(ctx, keyName)
	}
	// Default mock behavior: return valid JSON config for stripe
	if keyName == "stripe" {
		return `{"secretKey":"sk_test_123","webhookSecret":"whsec_123"}`, nil
	}
	return "", nil
}

// mockStripeClient mocks the stripe client
type mockStripeClient struct {
	createCheckoutFunc func(secretKey string, req CreateCheckoutRequest) (string, string, error)
}

func (m *mockStripeClient) CreateCheckoutSession(secretKey string, req CreateCheckoutRequest) (string, string, error) {
	if m.createCheckoutFunc != nil {
		return m.createCheckoutFunc(secretKey, req)
	}
	return "sess_123", "https://checkout.stripe.com/sess_123", nil
}

func TestCreateCheckout_Success(t *testing.T) {
	mockCreds := &mockPaymentCredentialsService{}
	mockStripe := &mockStripeClient{}

	handler := &PaymentHandler{
		credentialsService: mockCreds,
		stripeClient:       mockStripe,
	}

	reqBody := CreateCheckoutRequest{
		JobID:      1,
		PriceID:    "price_123",
		SuccessURL: "http://success",
		CancelURL:  "http://cancel",
	}
	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
	rr := httptest.NewRecorder()

	handler.CreateCheckout(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)

	var resp CreateCheckoutResponse
	err := json.Unmarshal(rr.Body.Bytes(), &resp)
	assert.NoError(t, err)
	assert.Equal(t, "sess_123", resp.SessionID)
}

func TestCreateCheckout_MissingFields(t *testing.T) {
	handler := &PaymentHandler{}
	reqBody := CreateCheckoutRequest{
		JobID: 0, // Invalid
	}
	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
	rr := httptest.NewRecorder()

	handler.CreateCheckout(rr, req)

	assert.Equal(t, http.StatusBadRequest, rr.Code)
}

func TestCreateCheckout_StripeError(t *testing.T) {
	mockCreds := &mockPaymentCredentialsService{}
	mockStripe := &mockStripeClient{
		createCheckoutFunc: func(secretKey string, req CreateCheckoutRequest) (string, string, error) {
			return "", "", errors.New("stripe error")
		},
	}

	handler := &PaymentHandler{
		credentialsService: mockCreds,
		stripeClient:       mockStripe,
	}

	reqBody := CreateCheckoutRequest{
		JobID:   1,
		PriceID: "price_123",
	}
	body, _ := json.Marshal(reqBody)
	req := httptest.NewRequest("POST", "/payments/create-checkout", bytes.NewReader(body))
	rr := httptest.NewRecorder()

	handler.CreateCheckout(rr, req)

	assert.Equal(t, http.StatusInternalServerError, rr.Code)
}

func TestGetPaymentStatus(t *testing.T) {
	handler := &PaymentHandler{}
	req := httptest.NewRequest("GET", "/payments/status/pay_123", nil)
	req.SetPathValue("id", "pay_123")
	rr := httptest.NewRecorder()

	handler.GetPaymentStatus(rr, req)

	assert.Equal(t, http.StatusOK, rr.Code)
	var resp map[string]interface{}
	json.Unmarshal(rr.Body.Bytes(), &resp)
	assert.Equal(t, "pay_123", resp["id"])
}

func TestHandleWebhook_Success(t *testing.T) {
	mockCreds := &mockPaymentCredentialsService{
		getDecryptedKeyFunc: func(ctx context.Context, keyName string) (string, error) {
			// Return config with secret
			return `{"webhookSecret":"whsec_test"}`, nil
		},
	}
	// Strategy for Webhook test:
	// VerifyStripeSignature is a standalone function that calculates HMAC.
	// It's hard to mock unless we export it or wrap it.
	// However, we can construct a valid signature for the test!
	// Or we can mock the signature verification if we wrapper it?
	// The current PaymentHandler calls `verifyStripeSignature` directly.
	// To test HandleWebhook fully, we need to generate a valid signature.

	secret := "whsec_test"
	payload := `{"type":"payment_intent.succeeded", "data":{}}`
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)

	// manually compute signature
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, payload)))
	sig := hex.EncodeToString(mac.Sum(nil))

	header := fmt.Sprintf("t=%s,v1=%s", timestamp, sig)

	req := httptest.NewRequest("POST", "/payments/webhook", bytes.NewReader([]byte(payload)))
	req.Header.Set("Stripe-Signature", header)
	rr := httptest.NewRecorder()

	handler := &PaymentHandler{
		credentialsService: mockCreds,
	}

	handler.HandleWebhook(rr, req)
	assert.Equal(t, http.StatusOK, rr.Code)
}

func TestHandleWebhook_CheckoutCompleted(t *testing.T) {
	mockCreds := &mockPaymentCredentialsService{
		getDecryptedKeyFunc: func(ctx context.Context, keyName string) (string, error) {
			return `{"webhookSecret":"whsec_test"}`, nil
		},
	}

	// Create payload for checkout.session.completed
	// logic: handleCheckoutComplete extracts ClientReferenceID -> JobID
	// And metadata -> userId, etc.
	// We need to match what handleCheckoutComplete expects.
	// It parses event.Data.Object into stripe.CheckoutSession.
	// Then calls jobService ... wait.
	// PaymentHandler NO LONGER depends on JobService directly? In Refactor I removed it?
	// Let's check PaymentHandler code.
	// If it doesn't have JobService, how does it update Job?
	// It calls `handlePaymentSuccess`.
	// I need to see what `handlePaymentSuccess` does.

	// Assuming logic is simple DB update or logging for now.

	secret := "whsec_test"
	payload := `{"type":"checkout.session.completed", "data":{"object":{"client_reference_id":"123", "metadata":{"userId":"u1"}, "payment_status":"paid"}}}`
	timestamp := strconv.FormatInt(time.Now().Unix(), 10)

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(fmt.Sprintf("%s.%s", timestamp, payload)))
	sig := hex.EncodeToString(mac.Sum(nil))

	header := fmt.Sprintf("t=%s,v1=%s", timestamp, sig)

	req := httptest.NewRequest("POST", "/payments/webhook", bytes.NewReader([]byte(payload)))
	req.Header.Set("Stripe-Signature", header)
	rr := httptest.NewRecorder()

	handler := &PaymentHandler{
		credentialsService: mockCreds,
	}

	handler.HandleWebhook(rr, req)
	assert.Equal(t, http.StatusOK, rr.Code)
}
