package nginx

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

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

// Config represents the NGINX deployment target configuration.
// This configuration is used on the agent side to deploy certificates to NGINX.
type Config struct {
	CertPath        string `json:"cert_path"`        // Path where cert will be written (typically /etc/nginx/certs/cert.pem)
	KeyPath         string `json:"key_path"`         // Path where private key will be written (NOT provided by control plane)
	ChainPath       string `json:"chain_path"`       // Path where chain will be written (typically /etc/nginx/certs/chain.pem)
	ReloadCommand   string `json:"reload_command"`   // Command to reload NGINX (e.g., "nginx -s reload" or "systemctl reload nginx")
	ValidateCommand string `json:"validate_command"` // Command to validate NGINX config (e.g., "nginx -t")
}

// Connector implements the target.Connector interface for NGINX servers.
// This connector runs on the AGENT side and handles local certificate deployment.
type Connector struct {
	config *Config
	logger *slog.Logger
}

// New creates a new NGINX target connector with the given configuration and logger.
func New(config *Config, logger *slog.Logger) *Connector {
	return &Connector{
		config: config,
		logger: logger,
	}
}

// ValidateConfig checks that all required configuration paths and commands are valid.
// It verifies that the certificate and key paths are writable and commands are executable.
func (c *Connector) ValidateConfig(ctx context.Context, rawConfig json.RawMessage) error {
	var cfg Config
	if err := json.Unmarshal(rawConfig, &cfg); err != nil {
		return fmt.Errorf("invalid NGINX config: %w", err)
	}

	if cfg.CertPath == "" || cfg.ChainPath == "" {
		return fmt.Errorf("NGINX cert_path and chain_path are required")
	}

	if cfg.ReloadCommand == "" || cfg.ValidateCommand == "" {
		return fmt.Errorf("NGINX reload_command and validate_command are required")
	}

	// Validate commands to prevent injection attacks
	if err := validation.ValidateShellCommand(cfg.ReloadCommand); err != nil {
		return fmt.Errorf("invalid reload_command: %w", err)
	}
	if err := validation.ValidateShellCommand(cfg.ValidateCommand); err != nil {
		return fmt.Errorf("invalid validate_command: %w", err)
	}

	c.logger.Info("validating NGINX configuration",
		"cert_path", cfg.CertPath,
		"chain_path", cfg.ChainPath)

	// Verify directory exists and is writable
	certDir := cfg.CertPath[:len(cfg.CertPath)-len("/cert.pem")] // Simple path extraction
	if _, err := os.Stat(certDir); os.IsNotExist(err) {
		return fmt.Errorf("NGINX cert directory does not exist: %s", certDir)
	}

	// Verify validate command works
	cmd := exec.CommandContext(ctx, cfg.ValidateCommand)
	if err := cmd.Run(); err != nil {
		c.logger.Warn("NGINX config validation failed during config check",
			"error", err,
			"validate_command", cfg.ValidateCommand)
		// Don't fail validation; NGINX might not be installed yet
	}

	c.config = &cfg
	c.logger.Info("NGINX configuration validated")
	return nil
}

// DeployCertificate writes the certificate and chain to the configured paths
// and reloads NGINX to pick up the new certificates.
// The agent (not the control plane) manages the private key.
//
// Steps:
// 1. Write certificate to cert_path with mode 0644 (readable by all)
// 2. Write chain to chain_path with mode 0644
// 3. Validate NGINX configuration
// 4. Execute reload command
func (c *Connector) DeployCertificate(ctx context.Context, request target.DeploymentRequest) (*target.DeploymentResult, error) {
	c.logger.Info("deploying certificate to NGINX",
		"cert_path", c.config.CertPath,
		"chain_path", c.config.ChainPath)

	startTime := time.Now()

	// Write certificate with secure permissions (0644: rw-r--r--)
	if err := os.WriteFile(c.config.CertPath, []byte(request.CertPEM), 0644); err != nil {
		errMsg := fmt.Sprintf("failed to write certificate: %v", err)
		c.logger.Error("certificate deployment failed", "error", err)
		return &target.DeploymentResult{
			Success:       false,
			TargetAddress: c.config.CertPath,
			Message:       errMsg,
			DeployedAt:    time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	// Write chain with same permissions
	if err := os.WriteFile(c.config.ChainPath, []byte(request.ChainPEM), 0644); err != nil {
		errMsg := fmt.Sprintf("failed to write chain: %v", err)
		c.logger.Error("chain deployment failed", "error", err)
		return &target.DeploymentResult{
			Success:       false,
			TargetAddress: c.config.ChainPath,
			Message:       errMsg,
			DeployedAt:    time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	// Validate NGINX configuration before reload
	c.logger.Debug("validating NGINX configuration", "validate_command", c.config.ValidateCommand)
	validateCmd := exec.CommandContext(ctx, c.config.ValidateCommand)
	if output, err := validateCmd.CombinedOutput(); err != nil {
		errMsg := fmt.Sprintf("NGINX config validation failed: %v (output: %s)", err, string(output))
		c.logger.Error("NGINX validation failed", "error", err, "output", string(output))
		return &target.DeploymentResult{
			Success:       false,
			TargetAddress: c.config.CertPath,
			Message:       errMsg,
			DeployedAt:    time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	// Reload NGINX
	c.logger.Debug("reloading NGINX", "reload_command", c.config.ReloadCommand)
	reloadCmd := exec.CommandContext(ctx, c.config.ReloadCommand)
	if output, err := reloadCmd.CombinedOutput(); err != nil {
		errMsg := fmt.Sprintf("NGINX reload failed: %v (output: %s)", err, string(output))
		c.logger.Error("NGINX reload failed", "error", err, "output", string(output))
		return &target.DeploymentResult{
			Success:       false,
			TargetAddress: c.config.CertPath,
			Message:       errMsg,
			DeployedAt:    time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	deploymentDuration := time.Since(startTime)
	c.logger.Info("certificate deployed to NGINX successfully",
		"duration", deploymentDuration.String(),
		"cert_path", c.config.CertPath)

	return &target.DeploymentResult{
		Success:       true,
		TargetAddress: c.config.CertPath,
		DeploymentID:  fmt.Sprintf("nginx-%d", time.Now().Unix()),
		Message:       "Certificate deployed and NGINX reloaded successfully",
		DeployedAt:    time.Now(),
		Metadata: map[string]string{
			"cert_path":   c.config.CertPath,
			"chain_path":  c.config.ChainPath,
			"duration_ms": fmt.Sprintf("%d", deploymentDuration.Milliseconds()),
		},
	}, nil
}

// ValidateDeployment verifies that the deployed certificate is valid and accessible.
// It validates the NGINX configuration to ensure the certificate can be read.
//
// Steps:
// 1. Run validate command to check config syntax
// 2. Verify certificate file is readable
func (c *Connector) ValidateDeployment(ctx context.Context, request target.ValidationRequest) (*target.ValidationResult, error) {
	c.logger.Info("validating NGINX deployment",
		"certificate_id", request.CertificateID,
		"serial", request.Serial)

	startTime := time.Now()

	// Validate NGINX configuration
	validateCmd := exec.CommandContext(ctx, c.config.ValidateCommand)
	if err := validateCmd.Run(); err != nil {
		errMsg := fmt.Sprintf("NGINX config validation failed: %v", err)
		c.logger.Error("validation failed", "error", err)
		return &target.ValidationResult{
			Valid:         false,
			Serial:        request.Serial,
			TargetAddress: c.config.CertPath,
			Message:       errMsg,
			ValidatedAt:   time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	// Verify certificate file exists and is readable
	if _, err := os.Stat(c.config.CertPath); os.IsNotExist(err) {
		errMsg := fmt.Sprintf("certificate file not found: %s", c.config.CertPath)
		c.logger.Error("validation failed", "error", err)
		return &target.ValidationResult{
			Valid:         false,
			Serial:        request.Serial,
			TargetAddress: c.config.CertPath,
			Message:       errMsg,
			ValidatedAt:   time.Now(),
		}, fmt.Errorf("%s", errMsg)
	}

	validationDuration := time.Since(startTime)
	c.logger.Info("NGINX deployment validated successfully",
		"duration", validationDuration.String())

	return &target.ValidationResult{
		Valid:         true,
		Serial:        request.Serial,
		TargetAddress: c.config.CertPath,
		Message:       "NGINX configuration valid and certificate accessible",
		ValidatedAt:   time.Now(),
		Metadata: map[string]string{
			"validate_command": c.config.ValidateCommand,
			"duration_ms":      fmt.Sprintf("%d", validationDuration.Milliseconds()),
		},
	}, nil
}
