package ucca

import (
	"fmt"
	"os"
	"path/filepath"
	"runtime"
)

// graphCallerRel resolves a path relative to THIS source file (build-time location), so the
// graph data is findable under `go test` (cwd = package dir) regardless of working directory.
// In a built container the source is gone, so cwd-relative candidates carry the load instead.
func graphCallerRel(rel string) string {
	_, file, _, ok := runtime.Caller(0)
	if !ok {
		return ""
	}
	return filepath.Join(filepath.Dir(file), rel)
}

// firstExisting returns the first candidate path that exists with the requested kind (dir vs
// file). Empty candidates (e.g. unset env overrides) are skipped.
func firstExisting(candidates []string, wantDir bool) string {
	for _, p := range candidates {
		if p == "" {
			continue
		}
		info, err := os.Stat(p)
		if err != nil || info.IsDir() != wantDir {
			continue
		}
		return p
	}
	return ""
}

// LoadComplianceGraph loads the file-backed Compliance Execution Graph: the Registry join-key
// contract (obligations/obligation_join_keys.json — owned by the Obligation session) + our
// curated, accepted control mappings + evidence requirements. Locations are resolved across
// three layouts: dev (cwd = ai-compliance-sdk/, canonical contract at ../obligations), container
// (WORKDIR /app, data/ copied in incl. a synced data/obligations/ copy) and `go test`
// (cwd = package dir, via graphCallerRel). Fail-closed: a missing/invalid source returns an
// error so the handler serves 503 — never a half-built graph.
//
// NOTE: data/obligations/obligation_join_keys.json is a SYNCED COPY of the repo-root contract
// (the canonical owner is the Obligation session). Re-sync it when the Registry grows; dev/test
// prefer the canonical repo-root path, only the container falls back to the copy.
func LoadComplianceGraph() (*ObligationJoinKeys, *ControlMappingSet, *EvidenceRequirementSet, error) {
	joinPath := firstExisting([]string{
		os.Getenv("BP_OBLIGATION_JOIN_KEYS"),
		"../obligations/obligation_join_keys.json",
		graphCallerRel("../../../obligations/obligation_join_keys.json"),
		"data/obligations/obligation_join_keys.json",
		graphCallerRel("../../data/obligations/obligation_join_keys.json"),
	}, false)
	if joinPath == "" {
		return nil, nil, nil, fmt.Errorf("obligation_join_keys.json not found in any candidate path")
	}
	mapDir := firstExisting([]string{
		os.Getenv("BP_CONTROL_MAPPINGS_DIR"),
		"data/control_mappings",
		graphCallerRel("../../data/control_mappings"),
	}, true)
	if mapDir == "" {
		return nil, nil, nil, fmt.Errorf("control_mappings dir not found in any candidate path")
	}
	evDir := firstExisting([]string{
		os.Getenv("BP_EVIDENCE_DIR"),
		"data/evidence_requirements",
		graphCallerRel("../../data/evidence_requirements"),
	}, true)
	if evDir == "" {
		return nil, nil, nil, fmt.Errorf("evidence_requirements dir not found in any candidate path")
	}

	joins, err := LoadObligationJoinKeys(joinPath)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("load join keys (%s): %w", joinPath, err)
	}
	mappings, err := LoadControlMappings(mapDir)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("load control mappings (%s): %w", mapDir, err)
	}
	evidence, err := LoadEvidenceRequirements(evDir)
	if err != nil {
		return nil, nil, nil, fmt.Errorf("load evidence (%s): %w", evDir, err)
	}
	return joins, mappings, evidence, nil
}
