package services

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"database/sql"
	"encoding/base64"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"os"
	"sync"
)

type CredentialsService struct {
	DB *sql.DB
	// Cache for decrypted keys
	cache      map[string]string
	cacheMutex sync.RWMutex
}

func NewCredentialsService(db *sql.DB) *CredentialsService {
	return &CredentialsService{
		DB:    db,
		cache: make(map[string]string),
	}
}

// SaveCredentials saves the encrypted payload for a service
func (s *CredentialsService) SaveCredentials(ctx context.Context, serviceName, encryptedPayload, updatedBy string) error {
	query := `
		INSERT INTO external_services_credentials (service_name, encrypted_payload, updated_by, updated_at)
		VALUES ($1, $2, $3, NOW())
		ON CONFLICT (service_name)
		DO UPDATE SET 
			encrypted_payload = EXCLUDED.encrypted_payload,
			updated_by = EXCLUDED.updated_by,
			updated_at = NOW()
	`
	_, err := s.DB.ExecContext(ctx, query, serviceName, encryptedPayload, updatedBy)
	if err != nil {
		return err
	}

	// Invalidate cache
	s.cacheMutex.Lock()
	delete(s.cache, serviceName)
	s.cacheMutex.Unlock()

	return nil
}

// GetDecryptedKey retrieves and decrypts the key for a service
func (s *CredentialsService) GetDecryptedKey(ctx context.Context, serviceName string) (string, error) {
	// Check cache first
	// Cache DISABLED to support external updates from Backoffice
	/*
		s.cacheMutex.RLock()
		if val, ok := s.cache[serviceName]; ok {
			s.cacheMutex.RUnlock()
			return val, nil
		}
		s.cacheMutex.RUnlock()
	*/

	// Fetch from DB
	var encryptedPayload string
	query := `SELECT encrypted_payload FROM external_services_credentials WHERE service_name = $1`
	err := s.DB.QueryRowContext(ctx, query, serviceName).Scan(&encryptedPayload)
	if err == sql.ErrNoRows {
		return "", fmt.Errorf("credentials for service %s not found", serviceName)
	}
	if err != nil {
		return "", err
	}

	// Decrypt
	decrypted, err := s.decryptPayload(encryptedPayload)
	if err != nil {
		return "", fmt.Errorf("failed to decrypt credentials: %w", err)
	}

	// Update cache
	s.cacheMutex.Lock()
	s.cache[serviceName] = decrypted
	s.cacheMutex.Unlock()

	return decrypted, nil
}

func (s *CredentialsService) decryptPayload(encryptedPayload string) (string, error) {
	// 1. Decode Private Key from Env
	rawPrivateKey, err := base64.StdEncoding.DecodeString(os.Getenv("RSA_PRIVATE_KEY_BASE64"))
	if err != nil {
		return "", fmt.Errorf("failed to decode env RSA private key: %w", err)
	}

	block, _ := pem.Decode(rawPrivateKey)
	if block == nil {
		return "", fmt.Errorf("failed to parse PEM block containing the private key")
	}

	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		// Try generic PKCS8 if PKCS1 fails
		if key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes); err2 == nil {
			if rsaKey, ok := key.(*rsa.PrivateKey); ok {
				privKey = rsaKey
			} else {
				return "", fmt.Errorf("key is not RSA")
			}
		} else {
			return "", err
		}
	}

	// 2. Decode ciphertext
	ciphertext, err := base64.StdEncoding.DecodeString(encryptedPayload)
	if err != nil {
		return "", err
	}

	// 3. Decrypt using RSA-OAEP
	plaintext, err := rsa.DecryptOAEP(
		sha256.New(),
		rand.Reader,
		privKey,
		ciphertext,
		nil,
	)
	if err != nil {
		return "", err
	}

	return string(plaintext), nil
}

// ConfiguredService represents a service with saved credentials (without revealing the actual value)
type ConfiguredService struct {
	ServiceName  string `json:"service_name"`
	UpdatedAt    string `json:"updated_at"`
	UpdatedBy    string `json:"updated_by,omitempty"`
	IsConfigured bool   `json:"is_configured"`
}

// ListConfiguredServices returns all configured services without revealing credential values
func (s *CredentialsService) ListConfiguredServices(ctx context.Context) ([]ConfiguredService, error) {
	// Define all supported services
	allServices := []string{
		"appwrite",
		"stripe",
		"firebase",
		"cloudflare",
		"smtp",
		"s3",
		"lavinmq",
	}

	query := `
		SELECT service_name, updated_at, COALESCE(updated_by::text, '') as updated_by
		FROM external_services_credentials
	`
	rows, err := s.DB.QueryContext(ctx, query)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	// Map of configured services
	configured := make(map[string]ConfiguredService)
	for rows.Next() {
		var cs ConfiguredService
		if err := rows.Scan(&cs.ServiceName, &cs.UpdatedAt, &cs.UpdatedBy); err != nil {
			return nil, err
		}
		cs.IsConfigured = true
		configured[cs.ServiceName] = cs
	}

	// Build result with all services
	result := make([]ConfiguredService, 0, len(allServices))
	for _, name := range allServices {
		if cs, ok := configured[name]; ok {
			result = append(result, cs)
		} else {
			result = append(result, ConfiguredService{
				ServiceName:  name,
				IsConfigured: false,
			})
		}
	}

	return result, nil
}

// DeleteCredentials removes credentials for a service
func (s *CredentialsService) DeleteCredentials(ctx context.Context, serviceName string) error {
	query := `DELETE FROM external_services_credentials WHERE service_name = $1`
	_, err := s.DB.ExecContext(ctx, query, serviceName)
	if err != nil {
		return err
	}

	// Clear cache
	s.cacheMutex.Lock()
	delete(s.cache, serviceName)
	s.cacheMutex.Unlock()

	return nil
}

// EncryptPayload encrypts a payload using the derived public key
func (s *CredentialsService) EncryptPayload(payload string) (string, error) {
	// 1. Decode Private Key from Env (to derive Public Key)
	// In a real scenario, you might store Public Key separately, but we can derive it.
	rawPrivateKey, err := base64.StdEncoding.DecodeString(os.Getenv("RSA_PRIVATE_KEY_BASE64"))
	if err != nil {
		return "", fmt.Errorf("failed to decode env RSA private key: %w", err)
	}

	block, _ := pem.Decode(rawPrivateKey)
	if block == nil {
		return "", fmt.Errorf("failed to parse PEM block containing the private key")
	}

	privKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		// Try generic PKCS8 if PKCS1 fails
		if key, err2 := x509.ParsePKCS8PrivateKey(block.Bytes); err2 == nil {
			if rsaKey, ok := key.(*rsa.PrivateKey); ok {
				privKey = rsaKey
			} else {
				return "", fmt.Errorf("key is not RSA")
			}
		} else {
			return "", err
		}
	}

	pubKey := &privKey.PublicKey

	// 2. Encrypt using RSA-OAEP
	ciphertext, err := rsa.EncryptOAEP(
		sha256.New(),
		rand.Reader,
		pubKey,
		[]byte(payload),
		nil,
	)
	if err != nil {
		return "", fmt.Errorf("encryption failed: %w", err)
	}

	return base64.StdEncoding.EncodeToString(ciphertext), nil
}

// BootstrapCredentials checks if credentials are in DB, if not, migrates from Env
func (s *CredentialsService) BootstrapCredentials(ctx context.Context) error {
	// List of services and their env mapping
	services := map[string]func() interface{}{
		"stripe": func() interface{} {
			return map[string]string{
				"secretKey":      os.Getenv("STRIPE_SECRET_KEY"),
				"webhookSecret":  os.Getenv("STRIPE_WEBHOOK_SECRET"),
				"publishableKey": os.Getenv("STRIPE_PUBLISHABLE_KEY"),
			}
		},
		"payment_gateway": func() interface{} {
			return map[string]string{
				"merchantId":    os.Getenv("PAYMENT_GATEWAY_MERCHANT_ID"),
				"apiKey":        os.Getenv("PAYMENT_GATEWAY_API_KEY"),
				"endpoint":      os.Getenv("PAYMENT_GATEWAY_ENDPOINT"),
				"webhookSecret": os.Getenv("PAYMENT_GATEWAY_WEBHOOK_SECRET"),
			}
		},
		"storage": func() interface{} {
			return map[string]string{
				"endpoint":  os.Getenv("AWS_ENDPOINT"),
				"accessKey": os.Getenv("AWS_ACCESS_KEY_ID"),
				"secretKey": os.Getenv("AWS_SECRET_ACCESS_KEY"),
				"bucket":    os.Getenv("S3_BUCKET"),
				"region":    os.Getenv("AWS_REGION"),
			}
		},
		"lavinmq": func() interface{} {
			return map[string]string{
				"amqpUrl": os.Getenv("AMQP_URL"),
			}
		},
		"cloudflare_config": func() interface{} {
			return map[string]string{
				"apiToken": os.Getenv("CLOUDFLARE_API_TOKEN"),
				"zoneId":   os.Getenv("CLOUDFLARE_ZONE_ID"),
			}
		},
		"cpanel": func() interface{} {
			return map[string]string{
				"host":     os.Getenv("CPANEL_HOST"),
				"username": os.Getenv("CPANEL_USERNAME"),
				"apiToken": os.Getenv("CPANEL_API_TOKEN"),
			}
		},
	}

	for service, getEnvData := range services {
		// Check if already configured
		configured, err := s.isServiceConfigured(ctx, service)
		if err != nil {
			fmt.Printf("[CredentialsBootstrap] Error checking %s: %v\n", service, err)
			continue
		}

		if !configured {
			data := getEnvData()
			// Validate if env vars exist (naive check: at least one field not empty)
			hasData := false
			jsonBytes, _ := json.Marshal(data)
			var stringMap map[string]string
			json.Unmarshal(jsonBytes, &stringMap)
			for _, v := range stringMap {
				if v != "" {
					hasData = true
					break
				}
			}

			if hasData {
				fmt.Printf("[CredentialsBootstrap] Migrating %s from Env to DB...\n", service)
				encrypted, err := s.EncryptPayload(string(jsonBytes))
				if err != nil {
					fmt.Printf("[CredentialsBootstrap] Failed to encrypt %s: %v\n", service, err)
					continue
				}
				if err := s.SaveCredentials(ctx, service, encrypted, "system_bootstrap"); err != nil {
					fmt.Printf("[CredentialsBootstrap] Failed to save %s: %v\n", service, err)
				} else {
					fmt.Printf("[CredentialsBootstrap] Successfully migrated %s\n", service)
				}
			}
		}
	}
	return nil
}

func (s *CredentialsService) isServiceConfigured(ctx context.Context, serviceName string) (bool, error) {
	var exists bool
	query := `SELECT EXISTS(SELECT 1 FROM external_services_credentials WHERE service_name = $1)`
	err := s.DB.QueryRowContext(ctx, query, serviceName).Scan(&exists)
	return exists, err
}
