package dedicatedapi

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"os"
	"strings"
)

const (
	ProductTrainEvidenceAPIVersion = "cp9.tidbcloud.com/v1alpha1"
	ProductTrainEvidenceKind       = "DedicatedPremiumInterleavedProof"
)

type ProductTrainEvidence struct {
	APIVersion   string             `json:"api_version"`
	Kind         string             `json:"kind"`
	HostAPI      APIIdentity        `json:"host_api"`
	DedicatedAPI APIIdentity        `json:"dedicated_api"`
	Providers    ProviderPlacement  `json:"providers"`
	Steps        []ProductTrainStep `json:"steps"`
	Isolation    IsolationEvidence  `json:"isolation"`
	Outage       OutageEvidence     `json:"outage_recovery"`
	Cleanup      CleanupEvidence    `json:"cleanup"`
}

type APIIdentity struct {
	ServerID         string `json:"server_id"`
	EtcdID           string `json:"etcd_id"`
	DefinitionDigest string `json:"definition_digest"`
}

type ProviderPlacement struct {
	Premium   string `json:"premium"`
	Dedicated string `json:"dedicated"`
}

type ProductTrainStep struct {
	Sequence              int    `json:"sequence"`
	Product               string `json:"product"`
	Operation             string `json:"operation"`
	APIStatus             string `json:"api_status"`
	SQLStatus             string `json:"sql_status"`
	OtherProductSQLStatus string `json:"other_product_sql_status"`
	EvidencePath          string `json:"evidence_path,omitempty"`
}

type IsolationEvidence struct {
	ConflictingSchemasSeparated  bool `json:"conflicting_schemas_separated"`
	NoCrossWrites                bool `json:"no_cross_writes"`
	SecondaryExecutableWorkloads int  `json:"secondary_executable_workloads"`
}

// OutageEvidence proves failure isolation at the product seam, not merely at
// the Kubernetes API seam. The Premium product must remain usable while the
// Dedicated metadata API is unavailable, and Dedicated must recover without
// losing its persisted state.
type OutageEvidence struct {
	DedicatedAPIUnavailable bool   `json:"dedicated_api_unavailable"`
	PremiumAPIStatus        string `json:"premium_api_status"`
	PremiumSQLStatus        string `json:"premium_sql_status"`
	DedicatedRecovered      bool   `json:"dedicated_recovered"`
	DedicatedStatePreserved bool   `json:"dedicated_state_preserved"`
}

type CleanupEvidence struct {
	PremiumGone          bool `json:"premium_gone"`
	DedicatedGone        bool `json:"dedicated_gone"`
	HostObjectsGone      bool `json:"host_objects_gone"`
	DedicatedObjectsGone bool `json:"dedicated_objects_gone"`
	CloudResourcesGone   bool `json:"cloud_resources_gone"`
}

func LoadAndValidateProductTrainEvidence(path string) (*ProductTrainEvidence, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("read product-train evidence: %w", err)
	}
	var evidence ProductTrainEvidence
	if err := json.Unmarshal(data, &evidence); err != nil {
		return nil, fmt.Errorf("parse product-train evidence: %w", err)
	}
	if err := ValidateProductTrainEvidence(&evidence); err != nil {
		return nil, err
	}
	return &evidence, nil
}

func ValidateProductTrainEvidence(evidence *ProductTrainEvidence) error {
	if evidence == nil {
		return fmt.Errorf("product-train evidence is required")
	}
	if evidence.APIVersion != ProductTrainEvidenceAPIVersion || evidence.Kind != ProductTrainEvidenceKind {
		return fmt.Errorf("unsupported product-train evidence contract %q %q", evidence.APIVersion, evidence.Kind)
	}
	if err := validateAPIIdentity("host", evidence.HostAPI); err != nil {
		return err
	}
	if err := validateAPIIdentity("Dedicated", evidence.DedicatedAPI); err != nil {
		return err
	}
	if evidence.HostAPI.ServerID == evidence.DedicatedAPI.ServerID || evidence.HostAPI.EtcdID == evidence.DedicatedAPI.EtcdID {
		return fmt.Errorf("host and Dedicated API/etcd identities must be distinct")
	}
	if evidence.Providers.Premium != "upbound" || evidence.Providers.Dedicated != "classic" {
		return fmt.Errorf("provider placement must be premium=upbound and dedicated=classic")
	}
	required := map[string]map[string]bool{
		"premium":   {"create": false, "modify": false, "delete": false, "recreate": false},
		"dedicated": {"create": false, "modify": false, "delete": false, "recreate": false},
	}
	operationOrder := map[string]int{"create": 0, "modify": 1, "delete": 2, "recreate": 3}
	lastOperation := map[string]int{"premium": -1, "dedicated": -1}
	productTransitions := 0
	for index, step := range evidence.Steps {
		if step.Sequence != index+1 {
			return fmt.Errorf("product-train step sequence must be contiguous at index %d", index)
		}
		operations, ok := required[step.Product]
		if !ok {
			return fmt.Errorf("product-train step %d has unknown product %q", step.Sequence, step.Product)
		}
		if _, ok := operations[step.Operation]; !ok {
			return fmt.Errorf("product-train step %d has unsupported operation %q", step.Sequence, step.Operation)
		}
		if operationOrder[step.Operation] != lastOperation[step.Product]+1 {
			return fmt.Errorf("product-train %s operations are not ordered create, modify, delete, recreate", step.Product)
		}
		lastOperation[step.Product] = operationOrder[step.Operation]
		if index > 0 && evidence.Steps[index-1].Product != step.Product {
			productTransitions++
		}
		if index > 0 && evidence.Steps[index-1].Product == step.Product && step.Operation != "recreate" {
			return fmt.Errorf("product-train steps must interleave products; only delete/recreate may be adjacent for one product")
		}
		if step.APIStatus != "passed" {
			return fmt.Errorf("product-train step %d API status is %q", step.Sequence, step.APIStatus)
		}
		wantSQL := "passed"
		if step.Operation == "delete" {
			wantSQL = "gone"
		}
		if step.SQLStatus != wantSQL {
			return fmt.Errorf("product-train step %d SQL status is %q, want %q", step.Sequence, step.SQLStatus, wantSQL)
		}
		if step.OtherProductSQLStatus != "passed" && (step.Sequence != 1 || step.OtherProductSQLStatus != "not_created") {
			return fmt.Errorf("product-train step %d did not preserve the other product SQL result", step.Sequence)
		}
		operations[step.Operation] = true
	}
	if len(evidence.Steps) < 2 || evidence.Steps[0].Operation != "create" || evidence.Steps[1].Operation != "create" ||
		evidence.Steps[0].Product == evidence.Steps[1].Product || productTransitions < 4 {
		return fmt.Errorf("product-train evidence must start both products and interleave their operations")
	}
	for _, product := range []string{"premium", "dedicated"} {
		for _, operation := range []string{"create", "modify", "delete", "recreate"} {
			if !required[product][operation] {
				return fmt.Errorf("product-train evidence is missing %s %s", product, operation)
			}
		}
	}
	if !evidence.Isolation.ConflictingSchemasSeparated || !evidence.Isolation.NoCrossWrites || evidence.Isolation.SecondaryExecutableWorkloads != 0 {
		return fmt.Errorf("isolation evidence must prove separated schemas, no cross-writes, and zero secondary executable workloads")
	}
	if !evidence.Outage.DedicatedAPIUnavailable || evidence.Outage.PremiumAPIStatus != "passed" ||
		evidence.Outage.PremiumSQLStatus != "passed" || !evidence.Outage.DedicatedRecovered ||
		!evidence.Outage.DedicatedStatePreserved {
		return fmt.Errorf(
			"outage/recovery evidence must prove Dedicated API unavailability, Premium API and SQL continuity, and Dedicated recovery with preserved state",
		)
	}
	if !evidence.Cleanup.PremiumGone || !evidence.Cleanup.DedicatedGone || !evidence.Cleanup.HostObjectsGone ||
		!evidence.Cleanup.DedicatedObjectsGone || !evidence.Cleanup.CloudResourcesGone {
		return fmt.Errorf("cleanup evidence must prove both products, both APIs, and cloud resources gone")
	}
	return nil
}

func validateAPIIdentity(name string, identity APIIdentity) error {
	if strings.TrimSpace(identity.ServerID) == "" || strings.TrimSpace(identity.EtcdID) == "" {
		return fmt.Errorf("%s API and etcd identities are required", name)
	}
	digest := strings.TrimSpace(identity.DefinitionDigest)
	decoded, err := hex.DecodeString(digest)
	if err != nil || len(decoded) != 32 {
		return fmt.Errorf("%s definition digest must be a SHA-256 hex digest", name)
	}
	return nil
}
