package sourcebacked

import (
	"embed"
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"
)

const (
	RegistryAPIVersion = "cp9.source-backed/v1alpha1"
	RegistryKind       = "SourceBackedServiceContractRegistry"

	BackendK3DControlPlane  = "k3d-control-plane"
	BackendRealEKSDataPlane = "real-eks-data-plane"

	ImageWriterTiltLocalImage = "tilt-local-image"

	ImageProvenanceDurableRelease            = "durable-release"
	ImageProvenanceLifecycleManagedEphemeral = "lifecycle-managed-ephemeral"
)

//go:embed contracts.json
var registryFS embed.FS

type Registry struct {
	APIVersion           string                         `json:"apiVersion"`
	Kind                 string                         `json:"kind"`
	StagingImageReleases map[string]StagingImageRelease `json:"stagingImageReleases,omitempty"`
	Contracts            []Contract                     `json:"contracts"`
	byName               map[string]Contract
}

type Contract struct {
	Name                           string         `json:"name"`
	SourceBuildSupported           bool           `json:"sourceBuildSupported"`
	SupportStatus                  string         `json:"supportStatus"`
	Source                         Source         `json:"source"`
	StagingImage                   string         `json:"stagingImage"`
	StagingImagePullSecretRequired *bool          `json:"stagingImagePullSecretRequired,omitempty"`
	StagingImageProvenance         string         `json:"stagingImageProvenance,omitempty"`
	StagingImageValidUntil         string         `json:"stagingImageValidUntil,omitempty"`
	Backend                        Backend        `json:"backend"`
	RolloutTarget                  RolloutTarget  `json:"rolloutTarget"`
	ReadinessCheck                 ReadinessCheck `json:"readinessCheck"`
	ImageWriter                    ImageWriter    `json:"imageWriter"`
	EvidenceTarget                 EvidenceTarget `json:"evidenceTarget"`
}

// RequiresStagingImagePullSecret defaults omitted contracts to the existing
// ECR pull-secret behavior.
func (c *Contract) RequiresStagingImagePullSecret() bool {
	return c.StagingImagePullSecretRequired == nil || *c.StagingImagePullSecretRequired
}

type Source struct {
	Repo string `json:"repo"`
	Main string `json:"main"`
	Mode string `json:"mode"`
}

// StagingImageRelease records one durable source release and every contract
// that consumes its staging mirror. Issue #393 owns verifying and mechanically
// refreshing these declarations; feature-branch staging tags are never a
// release source.
type StagingImageRelease struct {
	Repository  string   `json:"repository"`
	ReleaseLine string   `json:"releaseLine"`
	Commit      string   `json:"commit"`
	Services    []string `json:"services"`
}

type Backend struct {
	Kind  string `json:"kind"`
	Scope string `json:"scope"`
}

type RolloutTarget struct {
	Kind      string `json:"kind"`
	Namespace string `json:"namespace"`
	Name      string `json:"name"`
	Container string `json:"container"`
}

type ReadinessCheck struct {
	Kind string `json:"kind"`
}

type ImageWriter struct {
	Kind     string `json:"kind"`
	Resource string `json:"resource"`
}

type EvidenceTarget struct {
	Kind    string `json:"kind"`
	Service string `json:"service"`
}

func DefaultRegistry() (*Registry, error) {
	data, err := registryFS.ReadFile("contracts.json")
	if err != nil {
		return nil, fmt.Errorf("read source-backed contracts: %w", err)
	}
	return LoadRegistry(data)
}

func LoadRegistry(data []byte) (*Registry, error) {
	var registry Registry
	if err := json.Unmarshal(data, &registry); err != nil {
		return nil, fmt.Errorf("parse source-backed contracts: %w", err)
	}
	if err := registry.validate(); err != nil {
		return nil, err
	}
	return &registry, nil
}

func (r *Registry) Contract(name string) (Contract, bool) {
	if r == nil {
		return Contract{}, false
	}
	if r.byName == nil {
		r.index()
	}
	contract, ok := r.byName[strings.TrimSpace(name)]
	return contract, ok
}

func (r *Registry) ValidateSelectedSourceBuildServices(services []string) error {
	if r == nil {
		return errors.New("source-backed registry is nil")
	}
	seen := map[string]bool{}
	for _, service := range services {
		service = strings.TrimSpace(service)
		if service == "" {
			return errors.New("source_build_services contains empty service")
		}
		if seen[service] {
			return fmt.Errorf("source_build_services contains duplicate service %s", service)
		}
		seen[service] = true
		contract, ok := r.Contract(service)
		if !ok {
			return fmt.Errorf("source_build_services contains unknown source-backed service %s", service)
		}
		if !contract.SourceBuildSupported {
			status := strings.TrimSpace(contract.SupportStatus)
			if status == "" {
				status = "unsupported"
			}
			return fmt.Errorf("source_build_services selected unsupported source-backed service %s: %s", service, status)
		}
		if strings.TrimSpace(contract.Source.Repo) == "" {
			return fmt.Errorf("source-backed service %s missing source repo", service)
		}
		if strings.TrimSpace(contract.Source.Main) == "" {
			return fmt.Errorf("source-backed service %s missing source main", service)
		}
		if strings.TrimSpace(contract.ImageWriter.Kind) == "" || contract.ImageWriter.Kind == "unsupported" {
			return fmt.Errorf("source-backed service %s missing supported image writer", service)
		}
		if strings.TrimSpace(contract.RolloutTarget.Kind) == "" {
			return fmt.Errorf("source-backed service %s missing rollout target", service)
		}
		if strings.TrimSpace(contract.ReadinessCheck.Kind) == "" {
			return fmt.Errorf("source-backed service %s missing readiness check", service)
		}
		if strings.TrimSpace(contract.EvidenceTarget.Kind) == "" {
			return fmt.Errorf("source-backed service %s missing evidence target", service)
		}
	}
	return nil
}

func (r *Registry) RealEKSDataPlaneContracts() []Contract {
	if r == nil {
		return nil
	}
	var contracts []Contract
	for i := range r.Contracts {
		contract := &r.Contracts[i]
		if contract.Backend.Kind == BackendRealEKSDataPlane {
			contracts = append(contracts, *contract)
		}
	}
	return contracts
}

func (r *Registry) validate() error {
	if strings.TrimSpace(r.APIVersion) != RegistryAPIVersion {
		return fmt.Errorf("source-backed registry apiVersion = %q", r.APIVersion)
	}
	if strings.TrimSpace(r.Kind) != RegistryKind {
		return fmt.Errorf("source-backed registry kind = %q", r.Kind)
	}
	if len(r.Contracts) == 0 {
		return errors.New("source-backed registry has no contracts")
	}
	releaseByContract, err := validateStagingImageReleases(r.StagingImageReleases)
	if err != nil {
		return err
	}
	r.byName = map[string]Contract{}
	for i := range r.Contracts {
		contract := &r.Contracts[i]
		release, hasRelease := releaseByContract[contract.Name]
		if err := validateContract(contract, release, hasRelease); err != nil {
			return err
		}
		if _, exists := r.byName[contract.Name]; exists {
			return fmt.Errorf("duplicate source-backed service contract %s", contract.Name)
		}
		r.byName[contract.Name] = *contract
	}
	for service := range releaseByContract {
		if _, exists := r.byName[service]; !exists {
			return fmt.Errorf("staging image release references unknown source-backed contract %s", service)
		}
	}
	return nil
}

func validateContract(contract *Contract, release StagingImageRelease, hasRelease bool) error {
	if strings.TrimSpace(contract.Name) == "" {
		return errors.New("source-backed contract missing name")
	}
	if strings.TrimSpace(contract.SupportStatus) == "" {
		return fmt.Errorf("source-backed contract %s missing supportStatus", contract.Name)
	}
	if strings.TrimSpace(contract.Backend.Kind) == "" {
		return fmt.Errorf("source-backed contract %s missing backend kind", contract.Name)
	}
	if strings.TrimSpace(contract.ImageWriter.Kind) == "" {
		return fmt.Errorf("source-backed contract %s missing image writer kind", contract.Name)
	}
	if strings.TrimSpace(contract.EvidenceTarget.Kind) == "" || strings.TrimSpace(contract.EvidenceTarget.Service) == "" {
		return fmt.Errorf("source-backed contract %s missing evidence target", contract.Name)
	}
	if !contract.RequiresStagingImagePullSecret() && strings.TrimSpace(contract.StagingImage) == "" {
		return fmt.Errorf("source-backed contract %s disables staging image pull secret without a staging image", contract.Name)
	}
	if strings.HasPrefix(imageTag(contract.StagingImage), "stg-") {
		return fmt.Errorf("source-backed contract %s uses ephemeral feature-branch staging image %q", contract.Name, contract.StagingImage)
	}
	if contract.Backend.Kind == BackendRealEKSDataPlane {
		switch contract.StagingImageProvenance {
		case ImageProvenanceDurableRelease:
			if contract.StagingImageValidUntil != "" {
				return fmt.Errorf("source-backed contract %s durable image must not expire", contract.Name)
			}
		case ImageProvenanceLifecycleManagedEphemeral:
			if _, err := time.Parse(time.RFC3339, contract.StagingImageValidUntil); err != nil {
				return fmt.Errorf("source-backed contract %s ephemeral image requires RFC3339 stagingImageValidUntil: %w", contract.Name, err)
			}
		default:
			return fmt.Errorf(
				"source-backed contract %s has invalid staging image provenance %q",
				contract.Name,
				contract.StagingImageProvenance,
			)
		}
	}
	if strings.HasPrefix(imageTag(contract.StagingImage), "release-v") && !hasRelease {
		return fmt.Errorf("source-backed contract %s uses a release image without staging image release provenance", contract.Name)
	}
	if hasRelease {
		if err := validateStagingImageReleaseContract(contract, release); err != nil {
			return err
		}
	}
	if contract.SourceBuildSupported {
		if strings.TrimSpace(contract.Source.Repo) == "" {
			return fmt.Errorf("source-backed contract %s missing source repo", contract.Name)
		}
		if strings.TrimSpace(contract.Source.Main) == "" {
			return fmt.Errorf("source-backed contract %s missing source main", contract.Name)
		}
		if strings.TrimSpace(contract.StagingImage) == "" {
			return fmt.Errorf("source-backed contract %s missing staging image", contract.Name)
		}
		if strings.TrimSpace(contract.ImageWriter.Resource) == "" {
			return fmt.Errorf("source-backed contract %s missing image writer resource", contract.Name)
		}
		if strings.TrimSpace(contract.RolloutTarget.Kind) == "" || strings.TrimSpace(contract.ReadinessCheck.Kind) == "" {
			return fmt.Errorf("source-backed contract %s missing rollout/readiness contract", contract.Name)
		}
	}
	return nil
}

func validateStagingImageReleases(releases map[string]StagingImageRelease) (map[string]StagingImageRelease, error) {
	byContract := map[string]StagingImageRelease{}
	for name, release := range releases {
		if strings.TrimSpace(name) == "" {
			return nil, errors.New("staging image release has an empty name")
		}
		if strings.TrimSpace(release.Repository) == "" {
			return nil, fmt.Errorf("staging image release %s missing repository", name)
		}
		if !isReleaseLine(release.ReleaseLine) {
			return nil, fmt.Errorf("staging image release %s releaseLine = %q", name, release.ReleaseLine)
		}
		if !isLowerHexSHA(release.Commit) {
			return nil, fmt.Errorf("staging image release %s commit must be a 40-character lowercase SHA", name)
		}
		if len(release.Services) == 0 {
			return nil, fmt.Errorf("staging image release %s has no services", name)
		}
		for _, service := range release.Services {
			service = strings.TrimSpace(service)
			if service == "" {
				return nil, fmt.Errorf("staging image release %s has an empty service", name)
			}
			if _, exists := byContract[service]; exists {
				return nil, fmt.Errorf("source-backed contract %s belongs to multiple staging image releases", service)
			}
			byContract[service] = release
		}
	}
	return byContract, nil
}

func validateStagingImageReleaseContract(contract *Contract, release StagingImageRelease) error {
	stagingTag := imageTag(contract.StagingImage)
	expectedTag := "release-" + release.ReleaseLine + "-" + release.Commit
	expectedRepository := "385595570414.dkr.ecr.us-west-2.amazonaws.com/" + release.Repository + ":"
	if !strings.HasPrefix(contract.StagingImage, expectedRepository) {
		return fmt.Errorf(
			"source-backed contract %s staging image %q does not use release repository %q",
			contract.Name,
			contract.StagingImage,
			expectedRepository,
		)
	}
	if stagingTag == "" || stagingTag != expectedTag {
		return fmt.Errorf(
			"source-backed contract %s staging image tag %q does not match staging image release %q",
			contract.Name,
			stagingTag,
			expectedTag,
		)
	}
	return nil
}

func isReleaseLine(value string) bool {
	if !strings.HasPrefix(value, "v") {
		return false
	}
	parts := strings.Split(strings.TrimPrefix(value, "v"), ".")
	if len(parts) < 2 {
		return false
	}
	for _, part := range parts {
		if part == "" {
			return false
		}
		for _, r := range part {
			if r < '0' || r > '9' {
				return false
			}
		}
	}
	return true
}

func imageTag(image string) string {
	lastSlash := strings.LastIndex(image, "/")
	lastColon := strings.LastIndex(image, ":")
	if lastColon <= lastSlash {
		return ""
	}
	return image[lastColon+1:]
}

func isLowerHexSHA(value string) bool {
	if len(value) != 40 {
		return false
	}
	for _, r := range value {
		if ('0' > r || r > '9') && ('a' > r || r > 'f') {
			return false
		}
	}
	return true
}

func (r *Registry) index() {
	r.byName = map[string]Contract{}
	for i := range r.Contracts {
		contract := &r.Contracts[i]
		r.byName[contract.Name] = *contract
	}
}
