// Package registry implements the "registry" trigger: matches inbound image
// push events from container registries (Docker Hub, Gitea, ghcr, generic
// webhooks, polling) against a repo + tag-pattern filter.
package registry

import (
	"context"
	"encoding/json"
	"fmt"
	"path"
	"strings"
	"time"

	"github.com/alexei/tinyforge/internal/workload/plugin"
)

// Config is the per-workload trigger config blob. Image is the
// fully-qualified image reference the workload deploys (e.g.
// "registry.example.com/owner/app"); a push of any matching tag fires a
// deploy. TagPattern is a path.Match glob ("*" matches all).
type Config struct {
	Image      string `json:"image"`
	TagPattern string `json:"tag_pattern"`
}

type trigger struct{}

func init() { plugin.RegisterTrigger(&trigger{}) }

func (*trigger) Kind() string { return "registry" }

func (*trigger) SchemaSample() any {
	return Config{
		Image:      "registry.example.com/owner/app",
		TagPattern: "v*",
	}
}

func (*trigger) Validate(cfg json.RawMessage) error {
	var c Config
	if len(cfg) == 0 {
		return fmt.Errorf("registry trigger: config is required")
	}
	if err := json.Unmarshal(cfg, &c); err != nil {
		return fmt.Errorf("registry trigger: invalid json: %w", err)
	}
	if strings.TrimSpace(c.Image) == "" {
		return fmt.Errorf("registry trigger: image is required")
	}
	pattern := c.TagPattern
	if pattern == "" {
		pattern = "*"
	}
	if _, err := path.Match(pattern, "probe"); err != nil {
		return fmt.Errorf("registry trigger: invalid tag_pattern %q: %w", pattern, err)
	}
	return nil
}

func (*trigger) Match(ctx context.Context, deps plugin.Deps, w plugin.Workload, evt plugin.InboundEvent) (*plugin.DeploymentIntent, error) {
	if evt.Kind != "image-push" || evt.Image == nil {
		return nil, nil
	}
	cfg, err := plugin.TriggerConfigOf[Config](w)
	if err != nil {
		return nil, fmt.Errorf("registry trigger: decode config: %w", err)
	}
	if !imageMatches(cfg.Image, fullRepo(evt.Image)) {
		return nil, nil
	}
	pattern := cfg.TagPattern
	if pattern == "" {
		pattern = "*"
	}
	matched, err := path.Match(pattern, evt.Image.Tag)
	if err != nil || !matched {
		return nil, nil
	}
	return &plugin.DeploymentIntent{
		Reason:      "registry-push",
		Reference:   evt.Image.Tag,
		Metadata:    map[string]string{"digest": evt.Image.Digest, "repo": evt.Image.Repo},
		TriggeredAt: time.Now().UTC(),
		TriggeredBy: "registry-webhook",
	}, nil
}

func fullRepo(e *plugin.ImagePushEvent) string {
	if e.Registry == "" {
		return e.Repo
	}
	return e.Registry + "/" + e.Repo
}

// imageMatches: registry host case-insensitive, path/owner/name exact.
// Single-segment refs (e.g. Docker Hub officials like "nginx") have no
// `/` and match by exact equality of the bare name.
func imageMatches(want, got string) bool {
	if want == got {
		return true
	}
	wIdx := strings.IndexByte(want, '/')
	gIdx := strings.IndexByte(got, '/')
	// Both single-segment: equality already failed above, so no match.
	if wIdx < 0 && gIdx < 0 {
		return false
	}
	// One side single-segment, the other qualified — does not match.
	if wIdx < 0 || gIdx < 0 {
		return false
	}
	wHost, wPath := want[:wIdx], want[wIdx:]
	gHost, gPath := got[:gIdx], got[gIdx:]
	return strings.EqualFold(wHost, gHost) && wPath == gPath
}
