package seals_test

import (
	"bytes"
	"encoding/json"
	"errors"
	"go/ast"
	"go/parser"
	"go/token"
	"io"
	"os"
	"path/filepath"
	"reflect"
	"strconv"
	"strings"
	"testing"
)

const (
	servingAttestationRequirementPath = "../host-requirements.json"
	rollupHandlerPath                 = "../admin_rollup.go"

	servingAttestationProtocolID = "nexuscrm.rollup-maintenance.serving-attestation.v1"
	servingAttestationHeader     = "NexusCRM-If-Serving-Attestation"
)

type dialectHostRequirements struct {
	SchemaVersion     string                `json:"schema_version"`
	RequiredProtocols []protocolRequirement `json:"required_protocols"`
}

type protocolRequirement struct {
	ProtocolID   string `json:"protocol_id"`
	Major        uint32 `json:"major"`
	MinimumMinor uint32 `json:"minimum_minor"`
}

// TestServingAttestationHostRequirementIsClosedAndForwardOnly makes the
// dialect's neutral L0 requirement independently reviewable in app-packs CI.
// The requirement is a protocol version, never a Core source revision, and its
// closed shape deliberately has no legacy If-Match alternative.
func TestServingAttestationHostRequirementIsClosedAndForwardOnly(t *testing.T) {
	raw, err := os.ReadFile(servingAttestationRequirementPath)
	if err != nil {
		t.Fatal(err)
	}

	decoder := json.NewDecoder(bytes.NewReader(raw))
	decoder.DisallowUnknownFields()
	var got dialectHostRequirements
	if err := decoder.Decode(&got); err != nil {
		t.Fatalf("decode %s: %v", servingAttestationRequirementPath, err)
	}
	if err := decoder.Decode(&struct{}{}); !errors.Is(err, io.EOF) {
		if err == nil {
			t.Fatalf("%s contains more than one JSON value", servingAttestationRequirementPath)
		}
		t.Fatalf("decode trailing %s content: %v", servingAttestationRequirementPath, err)
	}

	want := dialectHostRequirements{
		SchemaVersion: "nexuscrm.dialect-host-requirements/v1",
		RequiredProtocols: []protocolRequirement{{
			ProtocolID:   servingAttestationProtocolID,
			Major:        1,
			MinimumMinor: 0,
		}},
	}
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("host requirements = %#v, want the sole forward-only serving-attestation protocol %#v", got, want)
	}
}

// TestServingAttestationHostRequirementMatchesDialectSource binds the
// declarative requirement to the source it governs without resolving the
// dialect's external shared replacement. Runtime source must use exactly the
// application-specific request field and neutral attestation carrier; test
// files may still mention If-Match solely to prove that it is rejected.
func TestServingAttestationHostRequirementMatchesDialectSource(t *testing.T) {
	fileset := token.NewFileSet()
	file, err := parser.ParseFile(fileset, rollupHandlerPath, nil, 0)
	if err != nil {
		t.Fatal(err)
	}

	headerValues := stringConstants(t, file, "rollupServingAttestationRequestHeader")
	if !reflect.DeepEqual(headerValues, []string{servingAttestationHeader}) {
		t.Fatalf("rollupServingAttestationRequestHeader = %#v, want exactly %q", headerValues, servingAttestationHeader)
	}

	requestLiterals := 0
	attestationFields := 0
	ast.Inspect(file, func(node ast.Node) bool {
		literal, ok := node.(*ast.CompositeLit)
		if !ok || !isContractRollupRequest(literal.Type) {
			return true
		}
		requestLiterals++
		for _, element := range literal.Elts {
			keyValue, ok := element.(*ast.KeyValueExpr)
			if !ok {
				continue
			}
			key, ok := keyValue.Key.(*ast.Ident)
			if ok && key.Name == "ExpectedServingAttestation" {
				attestationFields++
			}
		}
		return true
	})
	if requestLiterals != 1 || attestationFields != 1 {
		t.Fatalf("contract.RollupRecalculateRequest literals = %d and ExpectedServingAttestation fields = %d, want one of each", requestLiterals, attestationFields)
	}

	assertNoLegacyServingPreconditionInRuntime(t)
}

func stringConstants(t *testing.T, file *ast.File, name string) []string {
	t.Helper()
	var values []string
	for _, declaration := range file.Decls {
		general, ok := declaration.(*ast.GenDecl)
		if !ok || general.Tok != token.CONST {
			continue
		}
		for _, specification := range general.Specs {
			valueSpec, ok := specification.(*ast.ValueSpec)
			if !ok {
				continue
			}
			for index, identifier := range valueSpec.Names {
				if identifier.Name != name {
					continue
				}
				if index >= len(valueSpec.Values) {
					t.Fatalf("constant %s has no explicit value", name)
				}
				literal, ok := valueSpec.Values[index].(*ast.BasicLit)
				if !ok || literal.Kind != token.STRING {
					t.Fatalf("constant %s must be a string literal", name)
				}
				value, err := strconv.Unquote(literal.Value)
				if err != nil {
					t.Fatalf("unquote constant %s: %v", name, err)
				}
				values = append(values, value)
			}
		}
	}
	return values
}

func isContractRollupRequest(expression ast.Expr) bool {
	selector, ok := expression.(*ast.SelectorExpr)
	if !ok || selector.Sel.Name != "RollupRecalculateRequest" {
		return false
	}
	packageName, ok := selector.X.(*ast.Ident)
	return ok && packageName.Name == "contract"
}

func assertNoLegacyServingPreconditionInRuntime(t *testing.T) {
	t.Helper()
	const dialectRoot = ".."
	rootSkip := map[string]bool{
		"seals":  true,
		"tools":  true,
		"vendor": true,
	}

	var violations []string
	err := filepath.Walk(dialectRoot, func(path string, info os.FileInfo, walkErr error) error {
		if walkErr != nil {
			return walkErr
		}
		if info.IsDir() {
			if path == dialectRoot {
				return nil
			}
			relative, err := filepath.Rel(dialectRoot, path)
			if err != nil {
				return err
			}
			if rootSkip[filepath.ToSlash(relative)] {
				return filepath.SkipDir
			}
			base := info.Name()
			if base == "testdata" || strings.HasPrefix(base, ".") || strings.HasPrefix(base, "_") {
				return filepath.SkipDir
			}
			return nil
		}
		if filepath.Ext(path) != ".go" || strings.HasSuffix(info.Name(), "_test.go") {
			return nil
		}

		file, err := parser.ParseFile(token.NewFileSet(), path, nil, 0)
		if err != nil {
			return err
		}
		ast.Inspect(file, func(node ast.Node) bool {
			switch typed := node.(type) {
			case *ast.Ident:
				if typed.Name == "ExpectedServingETag" {
					violations = append(violations, path+": ExpectedServingETag")
				}
			case *ast.BasicLit:
				if typed.Kind != token.STRING {
					break
				}
				value, err := strconv.Unquote(typed.Value)
				if err == nil && strings.EqualFold(value, "If-Match") {
					violations = append(violations, path+": If-Match")
				}
			}
			return true
		})
		return nil
	})
	if err != nil {
		t.Fatal(err)
	}
	if len(violations) > 0 {
		t.Fatalf("legacy representation-validator path remains in runtime source: %s", strings.Join(violations, ", "))
	}
}
