package probes

import (
	"context"
	"fmt"
	"strings"

	"github.com/dtoro/oikos/internal/actuator"
	"github.com/dtoro/oikos/internal/core/ports"
	"golang.org/x/crypto/ssh"
)

// SSHChecker runs ssh-script probes via the actuator. Constructed with a
// key source so the caller controls SSH key resolution.
type SSHChecker struct {
	pool     *actuator.DialPool
	signerFn func() (ssh.Signer, error)
}

func NewSSHChecker(pool *actuator.DialPool, signerFn func() (ssh.Signer, error)) *SSHChecker {
	return &SSHChecker{pool: pool, signerFn: signerFn}
}

func (c *SSHChecker) Check(ctx context.Context, def ports.CheckDef, target ports.Target) ports.CheckResult {
	cfg := configMap(def)
	script, _ := parseStr(cfg, "script")
	if script == "" {
		return ports.CheckResult{State: "unknown", Message: "no script in config"}
	}

	signer, err := c.signerFn()
	if err != nil {
		return ports.CheckResult{State: "unknown", Message: fmt.Sprintf("signer: %v", err)}
	}

	client, err := actuator.Dial(ctx, actuator.DialOptions{
		Host:   target.Host,
		User:   target.User,
		Signer: signer,
	})
	if err != nil {
		return ports.CheckResult{State: "unknown", Message: fmt.Sprintf("dial %s: %v", target.Host, err)}
	}
	defer client.Close()

	cmd := fmt.Sprintf("/opt/oikos/checks/%s", script)
	if target.Wrap != nil {
		cmd = target.Wrap(cmd)
	}

	output, err := actuator.RunCombinedOutput(ctx, client, cmd)
	if err != nil {
		return ports.CheckResult{
			Value:   -1,
			State:   "critical",
			Message: err.Error(),
		}
	}
	return parseSSHResult(output)
}

func parseSSHResult(output []byte) ports.CheckResult {
	line := strings.TrimSpace(string(output))
	var value float64
	if len(line) > 0 {
		parts := strings.SplitN(line, " ", 3)
		switch parts[0] {
		case "OK":
			if len(parts) > 1 {
				fmt.Sscanf(parts[1], "%f", &value)
			}
			return ports.CheckResult{State: "ok", Value: value, Message: line}
		case "WARN":
			if len(parts) > 1 {
				fmt.Sscanf(parts[1], "%f", &value)
			}
			return ports.CheckResult{State: "warning", Value: value, Message: line}
		case "CRIT":
			if len(parts) > 1 {
				fmt.Sscanf(parts[1], "%f", &value)
			}
			return ports.CheckResult{State: "critical", Value: value, Message: line}
		}
	}
	return ports.CheckResult{State: "unknown", Message: "unparseable output"}
}

// Registry maps check kinds to Checker implementations.
type Registry map[string]ports.Checker

func NewRegistry() Registry {
	return Registry{
		"http":       NewHTTPChecker(),
		"tcp":        NewTCPChecker(),
		"ping":       NewPingChecker(),
		"dns":        NewDNSChecker(),
		"vm-status":  nil,
		"ssh-script": nil,
		"backup-freshness": nil,
		"cert-expiry":      nil,
	}
}

func (r Registry) Get(kind string) ports.Checker { return r[kind] }
func (r Registry) Register(kind string, c ports.Checker) { r[kind] = c }