package proof

import (
	"encoding/json"
	"errors"
	"fmt"
	"maps"
	"os"
	"path/filepath"
	"strings"

	"github.com/tidbcloud/controlplane9/internal/cluster"
	"github.com/tidbcloud/controlplane9/internal/tilt"
)

const (
	ImageSourceSummaryFileName = "premium-image-source-summary.json"
	imageSourceEvidenceDirName = "evidence"
)

var (
	ErrImageSourceSummaryNotFound = errors.New("image source summary not found")
	ErrInvalidImageSourceSummary  = errors.New("invalid image source summary")
)

type rawImageSourceSummary struct {
	APIVersion              string                               `json:"apiVersion"`
	Kind                    string                               `json:"kind"`
	Mode                    string                               `json:"mode"`
	SharedBaseServices      []string                             `json:"sharedBaseServices"`
	PremiumServices         []string                             `json:"premiumServices"`
	StarterServices         []string                             `json:"starterServices"`
	DedicatedServices       []string                             `json:"dedicatedServices"`
	SourceBuildServices     []string                             `json:"sourceBuildServices"`
	StagingImageServices    []string                             `json:"stagingImageServices"`
	AlwaysLocalStubServices []string                             `json:"alwaysLocalStubServices"`
	GatedServices           []string                             `json:"gatedServices"`
	ServiceClassifications  map[string]ImageSourceClassification `json:"serviceClassifications"`
	Images                  map[string]string                    `json:"images"`
}

func ImageSourceSummaryPath(stateDir string, state *cluster.State) string {
	paths := tilt.PathsForState(stateDir, state)
	return filepath.Join(paths.ReportsDir, imageSourceEvidenceDirName, ImageSourceSummaryFileName)
}

func LoadImageSourceSummary(path string) (*ImageSource, error) {
	path = strings.TrimSpace(path)
	if path == "" {
		return nil, fmt.Errorf("%w: empty path", ErrImageSourceSummaryNotFound)
	}
	data, err := os.ReadFile(path)
	if err != nil {
		if errors.Is(err, os.ErrNotExist) {
			return nil, fmt.Errorf("%w: %s", ErrImageSourceSummaryNotFound, path)
		}
		return nil, fmt.Errorf("%w: read %s: %w", ErrInvalidImageSourceSummary, path, err)
	}
	var raw rawImageSourceSummary
	if err := json.Unmarshal(data, &raw); err != nil {
		return nil, fmt.Errorf("%w: %s: %w", ErrInvalidImageSourceSummary, path, err)
	}
	if err := validateRawImageSourceSummary(&raw); err != nil {
		return nil, fmt.Errorf("%w: %s: %w", ErrInvalidImageSourceSummary, path, err)
	}
	return &ImageSource{
		Status:                  StatusPassed,
		Mode:                    strings.TrimSpace(raw.Mode),
		SharedBaseServices:      append([]string(nil), raw.SharedBaseServices...),
		PremiumServices:         append([]string(nil), raw.PremiumServices...),
		StarterServices:         append([]string(nil), raw.StarterServices...),
		DedicatedServices:       append([]string(nil), raw.DedicatedServices...),
		SourceBuildServices:     append([]string(nil), raw.SourceBuildServices...),
		StagingImageServices:    append([]string(nil), raw.StagingImageServices...),
		AlwaysLocalStubServices: append([]string(nil), raw.AlwaysLocalStubServices...),
		GatedServices:           append([]string(nil), raw.GatedServices...),
		ServiceClassifications:  cloneImageSourceClassifications(raw.ServiceClassifications),
		Images:                  cloneStringMap(raw.Images),
		ArtifactPath:            path,
	}, nil
}

func validateRawImageSourceSummary(raw *rawImageSourceSummary) error {
	if raw == nil {
		return errors.New("summary is empty")
	}
	if strings.TrimSpace(raw.APIVersion) != "cp9.premium.image-source/v1alpha1" {
		return fmt.Errorf("apiVersion = %q", raw.APIVersion)
	}
	if strings.TrimSpace(raw.Kind) != "PremiumImageSourceSummary" {
		return fmt.Errorf("kind = %q", raw.Kind)
	}
	mode := strings.TrimSpace(raw.Mode)
	if mode != "fast-start" && mode != "all-source" {
		return fmt.Errorf("mode = %q", raw.Mode)
	}
	if raw.Images == nil {
		return errors.New("images is required")
	}
	for service := range raw.ServiceClassifications {
		classification := raw.ServiceClassifications[service]
		if strings.TrimSpace(service) == "" {
			return errors.New("serviceClassifications contains empty service")
		}
		if strings.TrimSpace(classification.Class) == "" {
			return fmt.Errorf("serviceClassifications[%s].class is required", service)
		}
		switch strings.TrimSpace(classification.Mode) {
		case "", "staging-image", "source-backed", "gated", "always-local-stub", "manifest":
		default:
			return fmt.Errorf("serviceClassifications[%s].mode = %q", service, classification.Mode)
		}
		if strings.TrimSpace(classification.Mode) == "source-backed" &&
			strings.TrimSpace(classification.SelectedImage) == "" {
			return fmt.Errorf("serviceClassifications[%s].selected_image is required for source-backed mode", service)
		}
	}
	return nil
}

func cloneStringMap(in map[string]string) map[string]string {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string]string, len(in))
	maps.Copy(out, in)
	return out
}

func cloneImageSourceClassifications(in map[string]ImageSourceClassification) map[string]ImageSourceClassification {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string]ImageSourceClassification, len(in))
	maps.Copy(out, in)
	return out
}
