package services_test

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"os"
	"regexp"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/rede5/gohorsejobs/backend/internal/services"
	"github.com/stretchr/testify/assert"
)

// Helper to generate a valid RSA private key for testing
func generateTestRSAKey() (string, *rsa.PublicKey, error) {
	key, err := rsa.GenerateKey(rand.Reader, 2048)
	if err != nil {
		return "", nil, err
	}

	keyBytes := x509.MarshalPKCS1PrivateKey(key)
	pemBlock := &pem.Block{
		Type:  "RSA PRIVATE KEY",
		Bytes: keyBytes,
	}
	pemBytes := pem.EncodeToMemory(pemBlock)

	return base64.StdEncoding.EncodeToString(pemBytes), &key.PublicKey, nil
}

func TestSaveCredentials(t *testing.T) {
	db, mock, err := sqlmock.New()
	assert.NoError(t, err)
	defer db.Close()

	service := services.NewCredentialsService(db)
	ctx := context.Background()

	mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO external_services_credentials`)).
		WithArgs("stripe", "encrypted_data", "admin").
		WillReturnResult(sqlmock.NewResult(1, 1))

	err = service.SaveCredentials(ctx, "stripe", "encrypted_data", "admin")
	assert.NoError(t, err)
}

func TestGetDecryptedKey(t *testing.T) {
	// Setup RSA Key
	privKeyStr, pubKey, err := generateTestRSAKey()
	assert.NoError(t, err)
	os.Setenv("RSA_PRIVATE_KEY_BASE64", privKeyStr)
	defer os.Unsetenv("RSA_PRIVATE_KEY_BASE64")

	db, mock, err := sqlmock.New()
	assert.NoError(t, err)
	defer db.Close()

	service := services.NewCredentialsService(db)
	ctx := context.Background()

	// Encrypt a secret
	secret := "my-secret-key"
	encryptedBytes, err := rsa.EncryptOAEP(
		sha256.New(),
		rand.Reader,
		pubKey,
		[]byte(secret),
		nil,
	)
	assert.NoError(t, err)
	encryptedPayload := base64.StdEncoding.EncodeToString(encryptedBytes)

	// Mock DB return
	mock.ExpectQuery(regexp.QuoteMeta(`SELECT encrypted_payload FROM external_services_credentials`)).
		WithArgs("stripe").
		WillReturnRows(sqlmock.NewRows([]string{"encrypted_payload"}).AddRow(encryptedPayload))

	// Execute
	decrypted, err := service.GetDecryptedKey(ctx, "stripe")
	assert.NoError(t, err)
	assert.Equal(t, secret, decrypted)
}

func TestListConfiguredServices(t *testing.T) {
	db, mock, err := sqlmock.New()
	assert.NoError(t, err)
	defer db.Close()

	service := services.NewCredentialsService(db)
	ctx := context.Background()

	mock.ExpectQuery(regexp.QuoteMeta(`SELECT service_name, updated_at, COALESCE(updated_by::text, '')`)).
		WillReturnRows(sqlmock.NewRows([]string{"service_name", "updated_at", "updated_by"}).
			AddRow("stripe", time.Now().Format(time.RFC3339), "admin").
			AddRow("appwrite", time.Now().Format(time.RFC3339), "system"))

	servicesList, err := service.ListConfiguredServices(ctx)
	assert.NoError(t, err)
	assert.NotEmpty(t, servicesList)

	// Verify mapped correctly
	foundStripe := false
	for _, s := range servicesList {
		if s.ServiceName == "stripe" {
			assert.True(t, s.IsConfigured)
			foundStripe = true
		}
		if s.ServiceName == "firebase" {
			assert.False(t, s.IsConfigured)
		}
	}
	assert.True(t, foundStripe)
}

func TestDeleteCredentials(t *testing.T) {
	db, mock, err := sqlmock.New()
	assert.NoError(t, err)
	defer db.Close()

	service := services.NewCredentialsService(db)
	ctx := context.Background()

	mock.ExpectExec(regexp.QuoteMeta(`DELETE FROM external_services_credentials`)).
		WithArgs("stripe").
		WillReturnResult(sqlmock.NewResult(1, 1))

	err = service.DeleteCredentials(ctx, "stripe")
	assert.NoError(t, err)
}

func TestEncryptPayload(t *testing.T) {
	// Setup RSA Key
	privKeyStr, _, err := generateTestRSAKey()
	assert.NoError(t, err)
	os.Setenv("RSA_PRIVATE_KEY_BASE64", privKeyStr)
	defer os.Unsetenv("RSA_PRIVATE_KEY_BASE64")

	service := services.NewCredentialsService(nil)

	// Encrypt
	payload := "test-payload-123"
	encrypted, err := service.EncryptPayload(payload)
	assert.NoError(t, err)
	assert.NotEmpty(t, encrypted)
	assert.NotEqual(t, payload, encrypted)
}
