package mfer

import (
	"bytes"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
)

// GPGKeyID represents a GPG key identifier (fingerprint or key ID).
type GPGKeyID string

// SigningOptions contains options for GPG signing.
type SigningOptions struct {
	KeyID GPGKeyID
}

// gpgSign creates a detached signature of the data using the specified key.
// Returns the armored detached signature.
func gpgSign(data []byte, keyID GPGKeyID) ([]byte, error) {
	cmd := exec.Command("gpg",
		"--detach-sign",
		"--armor",
		"--local-user", string(keyID),
	)

	cmd.Stdin = bytes.NewReader(data)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("gpg sign failed: %w: %s", err, stderr.String())
	}

	return stdout.Bytes(), nil
}

// gpgExportPublicKey exports the public key for the specified key ID.
// Returns the armored public key.
func gpgExportPublicKey(keyID GPGKeyID) ([]byte, error) {
	cmd := exec.Command("gpg",
		"--export",
		"--armor",
		string(keyID),
	)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("gpg export failed: %w: %s", err, stderr.String())
	}

	if stdout.Len() == 0 {
		return nil, fmt.Errorf("gpg key not found: %s", keyID)
	}

	return stdout.Bytes(), nil
}

// gpgGetKeyFingerprint gets the full fingerprint for a key ID.
func gpgGetKeyFingerprint(keyID GPGKeyID) ([]byte, error) {
	cmd := exec.Command("gpg",
		"--with-colons",
		"--fingerprint",
		string(keyID),
	)

	var stdout, stderr bytes.Buffer
	cmd.Stdout = &stdout
	cmd.Stderr = &stderr

	if err := cmd.Run(); err != nil {
		return nil, fmt.Errorf("gpg fingerprint lookup failed: %w: %s", err, stderr.String())
	}

	// Parse the colon-delimited output to find the fingerprint
	lines := strings.Split(stdout.String(), "\n")
	for _, line := range lines {
		fields := strings.Split(line, ":")
		if len(fields) >= 10 && fields[0] == "fpr" {
			return []byte(fields[9]), nil
		}
	}

	return nil, fmt.Errorf("fingerprint not found for key: %s", keyID)
}

// gpgExtractPubKeyFingerprint imports a public key into a temporary keyring
// and extracts its fingerprint. This verifies the key is valid and returns
// the actual fingerprint from the key material.
func gpgExtractPubKeyFingerprint(pubKey []byte) (string, error) {
	// Create temporary directory for GPG operations
	tmpDir, err := os.MkdirTemp("", "mfer-gpg-fingerprint-*")
	if err != nil {
		return "", fmt.Errorf("failed to create temp dir: %w", err)
	}
	defer os.RemoveAll(tmpDir)

	// Set restrictive permissions
	if err := os.Chmod(tmpDir, 0o700); err != nil {
		return "", fmt.Errorf("failed to set temp dir permissions: %w", err)
	}

	// Write public key to temp file
	pubKeyFile := filepath.Join(tmpDir, "pubkey.asc")
	if err := os.WriteFile(pubKeyFile, pubKey, 0o600); err != nil {
		return "", fmt.Errorf("failed to write public key: %w", err)
	}

	// Import the public key into the temporary keyring
	importCmd := exec.Command("gpg",
		"--homedir", tmpDir,
		"--import",
		pubKeyFile,
	)
	var importStderr bytes.Buffer
	importCmd.Stderr = &importStderr
	if err := importCmd.Run(); err != nil {
		return "", fmt.Errorf("failed to import public key: %w: %s", err, importStderr.String())
	}

	// List keys to get fingerprint
	listCmd := exec.Command("gpg",
		"--homedir", tmpDir,
		"--with-colons",
		"--fingerprint",
	)
	var listStdout, listStderr bytes.Buffer
	listCmd.Stdout = &listStdout
	listCmd.Stderr = &listStderr
	if err := listCmd.Run(); err != nil {
		return "", fmt.Errorf("failed to list keys: %w: %s", err, listStderr.String())
	}

	// Parse the colon-delimited output to find the fingerprint
	lines := strings.Split(listStdout.String(), "\n")
	for _, line := range lines {
		fields := strings.Split(line, ":")
		if len(fields) >= 10 && fields[0] == "fpr" {
			return fields[9], nil
		}
	}

	return "", fmt.Errorf("fingerprint not found in imported key")
}

// gpgVerify verifies a detached signature against data using the provided public key.
// It creates a temporary keyring to import the public key for verification.
func gpgVerify(data, signature, pubKey []byte) error {
	// Create temporary directory for GPG operations
	tmpDir, err := os.MkdirTemp("", "mfer-gpg-verify-*")
	if err != nil {
		return fmt.Errorf("failed to create temp dir: %w", err)
	}
	defer os.RemoveAll(tmpDir)

	// Set restrictive permissions
	if err := os.Chmod(tmpDir, 0o700); err != nil {
		return fmt.Errorf("failed to set temp dir permissions: %w", err)
	}

	// Write public key to temp file
	pubKeyFile := filepath.Join(tmpDir, "pubkey.asc")
	if err := os.WriteFile(pubKeyFile, pubKey, 0o600); err != nil {
		return fmt.Errorf("failed to write public key: %w", err)
	}

	// Write signature to temp file
	sigFile := filepath.Join(tmpDir, "signature.asc")
	if err := os.WriteFile(sigFile, signature, 0o600); err != nil {
		return fmt.Errorf("failed to write signature: %w", err)
	}

	// Write data to temp file
	dataFile := filepath.Join(tmpDir, "data")
	if err := os.WriteFile(dataFile, data, 0o600); err != nil {
		return fmt.Errorf("failed to write data: %w", err)
	}

	// Import the public key into the temporary keyring
	importCmd := exec.Command("gpg",
		"--homedir", tmpDir,
		"--import",
		pubKeyFile,
	)
	var importStderr bytes.Buffer
	importCmd.Stderr = &importStderr
	if err := importCmd.Run(); err != nil {
		return fmt.Errorf("failed to import public key: %w: %s", err, importStderr.String())
	}

	// Verify the signature
	verifyCmd := exec.Command("gpg",
		"--homedir", tmpDir,
		"--verify",
		sigFile,
		dataFile,
	)
	var verifyStderr bytes.Buffer
	verifyCmd.Stderr = &verifyStderr
	if err := verifyCmd.Run(); err != nil {
		return fmt.Errorf("signature verification failed: %w: %s", err, verifyStderr.String())
	}

	return nil
}
