package keystore

import (
	"bufio"
	"encoding/base64"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"
)

// DNInfo represents Distinguished Name fields for the certificate
type DNInfo struct {
	CommonName         string `json:"cn"` // Required: App Name or Organization
	OrganizationalUnit string `json:"ou"` // Optional: Department
	Organization       string `json:"o"`  // Optional: Company Name
	Locality           string `json:"l"`  // Optional: City
	State              string `json:"st"` // Optional: State/Province
	Country            string `json:"c"`  // Optional: 2-letter country code
}

// GenerateRequest represents the request to generate a new keystore
type GenerateRequest struct {
	Alias         string `json:"alias"`
	StorePassword string `json:"store_password"`
	KeyPassword   string `json:"key_password"`
	ValidityDays  int    `json:"validity_days"` // Default: 9125 (~25 years)
	DN            DNInfo `json:"dn"`
}

// GenerateResponse contains the generated keystore and its fingerprints
type GenerateResponse struct {
	KeystoreBase64    string `json:"keystore_base64"`
	FingerprintSHA1   string `json:"fingerprint_sha1"`
	FingerprintSHA256 string `json:"fingerprint_sha256"`
}

// Validate validates the generate request
func (r *GenerateRequest) Validate() error {
	if r.Alias == "" {
		return fmt.Errorf("alias is required")
	}

	// Alias must start with letter, contain only letters, numbers, underscores, hyphens
	aliasRegex := regexp.MustCompile(`^[a-zA-Z][a-zA-Z0-9_-]*$`)
	if !aliasRegex.MatchString(r.Alias) {
		return fmt.Errorf("alias must start with a letter and contain only letters, numbers, underscores, and hyphens")
	}

	if r.StorePassword == "" {
		return fmt.Errorf("store_password is required")
	}

	if len(r.StorePassword) < 6 {
		return fmt.Errorf("store_password must be at least 6 characters")
	}

	if r.KeyPassword == "" {
		r.KeyPassword = r.StorePassword // Default to same as store password
	}

	if len(r.KeyPassword) < 6 {
		return fmt.Errorf("key_password must be at least 6 characters")
	}

	if r.DN.CommonName == "" {
		return fmt.Errorf("common name (cn) is required")
	}

	// Validate country code if provided
	if r.DN.Country != "" && len(r.DN.Country) != 2 {
		return fmt.Errorf("country must be a 2-letter code (e.g., US, UK, PH)")
	}

	// Set default validity if not provided
	if r.ValidityDays <= 0 {
		r.ValidityDays = 9125 // ~25 years
	}

	return nil
}

// sanitizeDNValue escapes special characters in DN field values
func sanitizeDNValue(s string) string {
	if s == "" {
		return s
	}
	// Escape characters that have special meaning in DN
	replacer := strings.NewReplacer(
		`\`, `\\`,
		`,`, `\,`,
		`"`, `\"`,
		`+`, `\+`,
		`<`, `\<`,
		`>`, `\>`,
		`;`, `\;`,
		`=`, `\=`,
	)
	return replacer.Replace(s)
}

// buildDNString constructs the Distinguished Name string for keytool
func buildDNString(dn DNInfo) string {
	var parts []string

	// CN is required
	parts = append(parts, fmt.Sprintf("CN=%s", sanitizeDNValue(dn.CommonName)))

	if dn.OrganizationalUnit != "" {
		parts = append(parts, fmt.Sprintf("OU=%s", sanitizeDNValue(dn.OrganizationalUnit)))
	}
	if dn.Organization != "" {
		parts = append(parts, fmt.Sprintf("O=%s", sanitizeDNValue(dn.Organization)))
	}
	if dn.Locality != "" {
		parts = append(parts, fmt.Sprintf("L=%s", sanitizeDNValue(dn.Locality)))
	}
	if dn.State != "" {
		parts = append(parts, fmt.Sprintf("ST=%s", sanitizeDNValue(dn.State)))
	}
	if dn.Country != "" {
		parts = append(parts, fmt.Sprintf("C=%s", strings.ToUpper(dn.Country)))
	}

	return strings.Join(parts, ", ")
}

// Generate creates a new JKS keystore using keytool
func Generate(req GenerateRequest) (*GenerateResponse, error) {
	// Validate request
	if err := req.Validate(); err != nil {
		return nil, err
	}

	// Create temporary directory
	tempDir, err := os.MkdirTemp("", "keystore-gen-*")
	if err != nil {
		return nil, fmt.Errorf("failed to create temp directory: %w", err)
	}
	defer os.RemoveAll(tempDir) // Clean up on exit

	keystorePath := filepath.Join(tempDir, "release.jks")
	dnString := buildDNString(req.DN)

	// Build keytool command
	// Note: Newer versions of keytool default to PKCS12 format
	// We explicitly use JKS format for maximum Android compatibility
	args := []string{
		"-genkeypair",
		"-alias", req.Alias,
		"-keyalg", "RSA",
		"-keysize", "2048",
		"-validity", fmt.Sprintf("%d", req.ValidityDays),
		"-storetype", "JKS",
		"-keystore", keystorePath,
		"-storepass", req.StorePassword,
		"-keypass", req.KeyPassword,
		"-dname", dnString,
	}

	// Execute keytool
	cmd := exec.Command("keytool", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return nil, fmt.Errorf("keytool failed: %s - %w", string(output), err)
	}

	// Extract fingerprints
	sha1, sha256, err := extractFingerprints(keystorePath, req.StorePassword)
	if err != nil {
		return nil, fmt.Errorf("failed to extract fingerprints: %w", err)
	}

	// Read and encode keystore
	keystoreData, err := os.ReadFile(keystorePath)
	if err != nil {
		return nil, fmt.Errorf("failed to read generated keystore: %w", err)
	}

	return &GenerateResponse{
		KeystoreBase64:    base64.StdEncoding.EncodeToString(keystoreData),
		FingerprintSHA1:   sha1,
		FingerprintSHA256: sha256,
	}, nil
}

// extractFingerprints gets SHA1 and SHA256 fingerprints from a keystore
func extractFingerprints(keystorePath, password string) (sha1, sha256 string, err error) {
	args := []string{
		"-list",
		"-v",
		"-keystore", keystorePath,
		"-storepass", password,
	}

	cmd := exec.Command("keytool", args...)
	output, err := cmd.CombinedOutput()
	if err != nil {
		return "", "", fmt.Errorf("keytool list failed: %s - %w", string(output), err)
	}

	// Parse fingerprints from output
	scanner := bufio.NewScanner(strings.NewReader(string(output)))
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())

		if strings.HasPrefix(line, "SHA1:") {
			sha1 = strings.TrimSpace(strings.TrimPrefix(line, "SHA1:"))
		} else if strings.HasPrefix(line, "SHA256:") {
			sha256 = strings.TrimSpace(strings.TrimPrefix(line, "SHA256:"))
		}
	}

	if sha1 == "" || sha256 == "" {
		return "", "", fmt.Errorf("could not extract fingerprints from keytool output")
	}

	return sha1, sha256, nil
}
