// Package wincertstore implements a target connector for deploying certificates
// to the Windows Certificate Store via PowerShell. Unlike the IIS connector,
// this connector only imports certificates into the store — it does not manage
// IIS site bindings. Use this for non-IIS Windows services that read certs
// from the Windows cert store (e.g., Exchange, RDP, SQL Server, ADFS).
//
// Architecture: Same injectable PowerShellExecutor pattern as the IIS connector.
// Supports agent-local PowerShell or WinRM proxy agent modes.
package wincertstore

import (
	"context"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"log/slog"
	"os/exec"
	"regexp"
	"strings"
	"time"

	"github.com/shankar0123/certctl/internal/connector/target"
	"github.com/shankar0123/certctl/internal/connector/target/certutil"
)

// Config represents the Windows Certificate Store deployment target configuration.
type Config struct {
	// StoreName is the Windows certificate store name (e.g., "My", "Root", "WebHosting").
	StoreName string `json:"store_name"`

	// StoreLocation is the store location: "LocalMachine" (default) or "CurrentUser".
	StoreLocation string `json:"store_location"`

	// FriendlyName is an optional friendly name assigned to the imported certificate.
	FriendlyName string `json:"friendly_name,omitempty"`

	// RemoveExpired controls whether expired certificates with the same CN are removed
	// after successful import. Default false.
	RemoveExpired bool `json:"remove_expired,omitempty"`

	// Mode is the deployment mode: "local" (default) or "winrm".
	Mode string `json:"mode"`

	// WinRM settings (only used when Mode is "winrm").
	WinRMHost     string `json:"winrm_host,omitempty"`
	WinRMPort     int    `json:"winrm_port,omitempty"`
	WinRMUsername string `json:"winrm_username,omitempty"`
	WinRMPassword string `json:"winrm_password,omitempty"`
	WinRMHTTPS    bool   `json:"winrm_https,omitempty"`
	WinRMInsecure bool   `json:"winrm_insecure,omitempty"`
}

// PowerShellExecutor abstracts PowerShell command execution for testability.
type PowerShellExecutor interface {
	Execute(ctx context.Context, script string) (string, error)
}

// realExecutor calls powershell.exe on the local system.
type realExecutor struct{}

func (e *realExecutor) Execute(ctx context.Context, script string) (string, error) {
	cmd := exec.CommandContext(ctx, "powershell.exe", "-NoProfile", "-NonInteractive", "-Command", script)
	out, err := cmd.CombinedOutput()
	return strings.TrimSpace(string(out)), err
}

// Connector implements the target.Connector interface for Windows Certificate Store.
type Connector struct {
	config   *Config
	logger   *slog.Logger
	executor PowerShellExecutor
}

// validStoreName matches safe Windows certificate store names (alphanumeric, spaces, hyphens, dots).
var validStoreName = regexp.MustCompile(`^[a-zA-Z0-9 _\-\.]+$`)

// validStoreLocation matches allowed store locations.
var validStoreLocations = map[string]bool{
	"LocalMachine": true,
	"CurrentUser":  true,
}

// New creates a new Windows Certificate Store connector with the default PowerShell executor.
func New(cfg *Config, logger *slog.Logger) (*Connector, error) {
	if cfg == nil {
		cfg = &Config{}
	}
	applyDefaults(cfg)
	return &Connector{
		config:   cfg,
		logger:   logger,
		executor: &realExecutor{},
	}, nil
}

// NewWithExecutor creates a connector with an injected executor for testing.
func NewWithExecutor(cfg *Config, logger *slog.Logger, executor PowerShellExecutor) *Connector {
	if cfg == nil {
		cfg = &Config{}
	}
	applyDefaults(cfg)
	return &Connector{
		config:   cfg,
		logger:   logger,
		executor: executor,
	}
}

func applyDefaults(cfg *Config) {
	if cfg.StoreName == "" {
		cfg.StoreName = "My"
	}
	if cfg.StoreLocation == "" {
		cfg.StoreLocation = "LocalMachine"
	}
	if cfg.Mode == "" {
		cfg.Mode = "local"
	}
}

// ValidateConfig validates the Windows Certificate Store configuration.
func (c *Connector) ValidateConfig(ctx context.Context, config json.RawMessage) error {
	var cfg Config
	if err := json.Unmarshal(config, &cfg); err != nil {
		return fmt.Errorf("invalid WinCertStore config JSON: %w", err)
	}
	applyDefaults(&cfg)

	if !validStoreName.MatchString(cfg.StoreName) {
		return fmt.Errorf("invalid store_name: must be alphanumeric (got %q)", cfg.StoreName)
	}

	if !validStoreLocations[cfg.StoreLocation] {
		return fmt.Errorf("invalid store_location: must be 'LocalMachine' or 'CurrentUser' (got %q)", cfg.StoreLocation)
	}

	if cfg.FriendlyName != "" && !validStoreName.MatchString(cfg.FriendlyName) {
		return fmt.Errorf("invalid friendly_name: must be alphanumeric (got %q)", cfg.FriendlyName)
	}

	if cfg.Mode != "local" && cfg.Mode != "winrm" {
		return fmt.Errorf("invalid mode: must be 'local' or 'winrm' (got %q)", cfg.Mode)
	}

	if cfg.Mode == "winrm" {
		if cfg.WinRMHost == "" {
			return fmt.Errorf("winrm_host is required when mode is 'winrm'")
		}
		if cfg.WinRMUsername == "" {
			return fmt.Errorf("winrm_username is required when mode is 'winrm'")
		}
		if cfg.WinRMPassword == "" {
			return fmt.Errorf("winrm_password is required when mode is 'winrm'")
		}
	}

	c.config = &cfg
	return nil
}

// DeployCertificate imports a certificate into the Windows Certificate Store.
func (c *Connector) DeployCertificate(ctx context.Context, request target.DeploymentRequest) (*target.DeploymentResult, error) {
	if request.KeyPEM == "" {
		return nil, fmt.Errorf("private key is required for Windows Certificate Store import")
	}

	c.logger.Info("deploying certificate to Windows Certificate Store",
		"store_name", c.config.StoreName,
		"store_location", c.config.StoreLocation)

	// Generate transient PFX password
	pfxPassword, err := certutil.GenerateRandomPassword(32)
	if err != nil {
		return nil, fmt.Errorf("generate PFX password: %w", err)
	}

	// Convert PEM to PFX
	pfxData, err := certutil.CreatePFX(request.CertPEM, request.KeyPEM, request.ChainPEM, pfxPassword)
	if err != nil {
		return nil, fmt.Errorf("create PFX: %w", err)
	}

	// Compute thumbprint for verification
	thumbprint, err := certutil.ComputeThumbprint(request.CertPEM)
	if err != nil {
		return nil, fmt.Errorf("compute thumbprint: %w", err)
	}

	// Build the PowerShell import script
	pfxB64 := base64.StdEncoding.EncodeToString(pfxData)
	script := c.buildImportScript(pfxB64, pfxPassword, thumbprint)

	output, err := c.executor.Execute(ctx, script)
	if err != nil {
		return nil, fmt.Errorf("PowerShell import failed: %s: %w", output, err)
	}

	c.logger.Info("certificate imported to Windows Certificate Store",
		"thumbprint", thumbprint,
		"store", c.config.StoreName,
		"location", c.config.StoreLocation)

	return &target.DeploymentResult{
		Success:       true,
		TargetAddress: fmt.Sprintf("cert:\\%s\\%s", c.config.StoreLocation, c.config.StoreName),
		DeploymentID:  thumbprint,
		Message:       fmt.Sprintf("Certificate imported to %s\\%s (thumbprint: %s)", c.config.StoreLocation, c.config.StoreName, thumbprint),
		DeployedAt:    time.Now(),
		Metadata: map[string]string{
			"thumbprint":     thumbprint,
			"store_name":     c.config.StoreName,
			"store_location": c.config.StoreLocation,
		},
	}, nil
}

// buildImportScript creates the PowerShell script to import a PFX into the cert store.
func (c *Connector) buildImportScript(pfxB64, pfxPassword, thumbprint string) string {
	var sb strings.Builder

	// Decode PFX from base64 and write to temp file
	sb.WriteString(fmt.Sprintf("$pfxBytes = [System.Convert]::FromBase64String('%s')\n", pfxB64))
	sb.WriteString("$pfxPath = [System.IO.Path]::GetTempFileName() + '.pfx'\n")
	sb.WriteString("try {\n")
	sb.WriteString("  [System.IO.File]::WriteAllBytes($pfxPath, $pfxBytes)\n")

	// Import PFX to cert store
	sb.WriteString(fmt.Sprintf("  $secPwd = ConvertTo-SecureString -String '%s' -Force -AsPlainText\n", pfxPassword))
	sb.WriteString(fmt.Sprintf("  $cert = Import-PfxCertificate -FilePath $pfxPath -CertStoreLocation 'Cert:\\%s\\%s' -Password $secPwd -Exportable\n",
		c.config.StoreLocation, c.config.StoreName))

	// Set friendly name if configured
	if c.config.FriendlyName != "" {
		sb.WriteString(fmt.Sprintf("  $cert.FriendlyName = '%s'\n", c.config.FriendlyName))
	}

	// Verify import
	sb.WriteString(fmt.Sprintf("  $imported = Get-ChildItem 'Cert:\\%s\\%s\\%s' -ErrorAction SilentlyContinue\n",
		c.config.StoreLocation, c.config.StoreName, thumbprint))
	sb.WriteString("  if (-not $imported) { throw 'Certificate import verification failed' }\n")

	// Remove expired certs with same subject (optional)
	if c.config.RemoveExpired {
		sb.WriteString("  $subject = $cert.Subject\n")
		sb.WriteString(fmt.Sprintf("  Get-ChildItem 'Cert:\\%s\\%s' | Where-Object { $_.Subject -eq $subject -and $_.NotAfter -lt (Get-Date) -and $_.Thumbprint -ne '%s' } | Remove-Item -Force\n",
			c.config.StoreLocation, c.config.StoreName, thumbprint))
	}

	sb.WriteString(fmt.Sprintf("  Write-Output 'SUCCESS:%s'\n", thumbprint))
	sb.WriteString("} finally {\n")
	sb.WriteString("  if (Test-Path $pfxPath) { Remove-Item $pfxPath -Force }\n")
	sb.WriteString("}\n")

	return sb.String()
}

// ValidateDeployment verifies that a certificate exists in the Windows Certificate Store.
func (c *Connector) ValidateDeployment(ctx context.Context, request target.ValidationRequest) (*target.ValidationResult, error) {
	// Get thumbprint from metadata if available, otherwise query by serial
	thumbprint := ""
	if request.Metadata != nil {
		thumbprint = request.Metadata["thumbprint"]
	}

	var script string
	if thumbprint != "" {
		script = fmt.Sprintf("$cert = Get-ChildItem 'Cert:\\%s\\%s\\%s' -ErrorAction SilentlyContinue; if ($cert) { Write-Output ('FOUND:' + $cert.Thumbprint + ':' + $cert.NotAfter.ToString('o')) } else { Write-Output 'NOT_FOUND' }",
			c.config.StoreLocation, c.config.StoreName, thumbprint)
	} else {
		// Fallback: search by serial number
		script = fmt.Sprintf("$cert = Get-ChildItem 'Cert:\\%s\\%s' | Where-Object { $_.SerialNumber -eq '%s' } | Select-Object -First 1; if ($cert) { Write-Output ('FOUND:' + $cert.Thumbprint + ':' + $cert.NotAfter.ToString('o')) } else { Write-Output 'NOT_FOUND' }",
			c.config.StoreLocation, c.config.StoreName, request.Serial)
	}

	output, err := c.executor.Execute(ctx, script)
	if err != nil {
		return &target.ValidationResult{
			Valid:       false,
			Serial:      request.Serial,
			Message:     fmt.Sprintf("PowerShell query failed: %s", output),
			ValidatedAt: time.Now(),
		}, fmt.Errorf("validation query failed: %w", err)
	}

	if strings.HasPrefix(output, "FOUND:") {
		parts := strings.SplitN(output, ":", 3)
		foundThumb := ""
		if len(parts) >= 2 {
			foundThumb = parts[1]
		}
		return &target.ValidationResult{
			Valid:         true,
			Serial:        request.Serial,
			TargetAddress: fmt.Sprintf("cert:\\%s\\%s", c.config.StoreLocation, c.config.StoreName),
			Message:       fmt.Sprintf("Certificate found in store (thumbprint: %s)", foundThumb),
			ValidatedAt:   time.Now(),
			Metadata: map[string]string{
				"thumbprint": foundThumb,
			},
		}, nil
	}

	return &target.ValidationResult{
		Valid:       false,
		Serial:      request.Serial,
		Message:     "Certificate not found in Windows Certificate Store",
		ValidatedAt: time.Now(),
	}, fmt.Errorf("certificate not found in %s\\%s", c.config.StoreLocation, c.config.StoreName)
}

// Ensure Connector implements target.Connector.
var _ target.Connector = (*Connector)(nil)

