package signing

import (
	"context"
	"fmt"
	"os"
	"os/exec"
	"runtime"
)

// MacOSSigner signs binaries using macOS codesign.
type MacOSSigner struct {
	config MacOSConfig
}

// Compile-time interface check.
var _ Signer = (*MacOSSigner)(nil)

// NewMacOSSigner creates a new macOS signer.
func NewMacOSSigner(cfg MacOSConfig) *MacOSSigner {
	return &MacOSSigner{config: cfg}
}

// Name returns "codesign".
func (s *MacOSSigner) Name() string {
	return "codesign"
}

// Available checks if running on macOS with codesign and identity configured.
func (s *MacOSSigner) Available() bool {
	if runtime.GOOS != "darwin" {
		return false
	}
	if s.config.Identity == "" {
		return false
	}
	_, err := exec.LookPath("codesign")
	return err == nil
}

// Sign codesigns a binary with hardened runtime.
func (s *MacOSSigner) Sign(ctx context.Context, binary string) error {
	if !s.Available() {
		return fmt.Errorf("codesign.Sign: codesign not available")
	}

	cmd := exec.CommandContext(ctx, "codesign",
		"--sign", s.config.Identity,
		"--timestamp",
		"--options", "runtime", // Hardened runtime for notarization
		"--force",
		binary,
	)

	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("codesign.Sign: %w\nOutput: %s", err, string(output))
	}

	return nil
}

// Notarize submits binary to Apple for notarization and staples the ticket.
// This blocks until Apple responds (typically 1-5 minutes).
func (s *MacOSSigner) Notarize(ctx context.Context, binary string) error {
	if s.config.AppleID == "" || s.config.TeamID == "" || s.config.AppPassword == "" {
		return fmt.Errorf("codesign.Notarize: missing Apple credentials (apple_id, team_id, app_password)")
	}

	// Create ZIP for submission
	zipPath := binary + ".zip"
	zipCmd := exec.CommandContext(ctx, "zip", "-j", zipPath, binary)
	if output, err := zipCmd.CombinedOutput(); err != nil {
		return fmt.Errorf("codesign.Notarize: failed to create zip: %w\nOutput: %s", err, string(output))
	}
	defer os.Remove(zipPath)

	// Submit to Apple and wait
	submitCmd := exec.CommandContext(ctx, "xcrun", "notarytool", "submit",
		zipPath,
		"--apple-id", s.config.AppleID,
		"--team-id", s.config.TeamID,
		"--password", s.config.AppPassword,
		"--wait",
	)
	if output, err := submitCmd.CombinedOutput(); err != nil {
		return fmt.Errorf("codesign.Notarize: notarization failed: %w\nOutput: %s", err, string(output))
	}

	// Staple the ticket
	stapleCmd := exec.CommandContext(ctx, "xcrun", "stapler", "staple", binary)
	if output, err := stapleCmd.CombinedOutput(); err != nil {
		return fmt.Errorf("codesign.Notarize: failed to staple: %w\nOutput: %s", err, string(output))
	}

	return nil
}

// ShouldNotarize returns true if notarization is enabled.
func (s *MacOSSigner) ShouldNotarize() bool {
	return s.config.Notarize
}
