package ucca

import (
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"runtime"
	"strings"
)

// CanonicalControl represents a single independently authored security control.
type CanonicalControl struct {
	ControlID            string             `json:"control_id"`
	Title                string             `json:"title"`
	Domain               string             `json:"domain"`
	Severity             string             `json:"severity"` // low, medium, high, critical
	RiskScore            float64            `json:"risk_score"`
	ImplementationEffort string             `json:"implementation_effort"` // s, m, l, xl
	Objective            string             `json:"objective"`
	Rationale            string             `json:"rationale"`
	Scope                CanonicalScope     `json:"scope"`
	Requirements         []string           `json:"requirements"`
	TestProcedure        []string           `json:"test_procedure"`
	Evidence             []CanonicalEvidence `json:"evidence"`
	OpenAnchors          []OpenAnchor       `json:"open_anchors"`
	Tags                 []string           `json:"tags"`
}

// CanonicalScope defines where a control applies.
type CanonicalScope struct {
	Platforms   []string `json:"platforms"`
	Components  []string `json:"components"`
	DataClasses []string `json:"data_classes"`
}

// CanonicalEvidence describes a required evidence item.
type CanonicalEvidence struct {
	Type        string `json:"type"`
	Description string `json:"description"`
}

// OpenAnchor links a control to an open-source framework reference.
type OpenAnchor struct {
	Framework string `json:"framework"`
	Ref       string `json:"ref"`
	URL       string `json:"url"`
}

// CanonicalDomain groups controls by security domain.
type CanonicalDomain struct {
	ID        string `json:"id"`
	Name      string `json:"name"`
	Objective string `json:"objective"`
}

// CanonicalFramework is the framework metadata.
type CanonicalFramework struct {
	ID          string `json:"id"`
	Name        string `json:"name"`
	Version     string `json:"version"`
	Description string `json:"description"`
}

// CanonicalControlLibrary is the top-level JSON structure.
type CanonicalControlLibrary struct {
	Version       string             `json:"version"`
	Schema        string             `json:"schema"`
	Generated     string             `json:"generated"`
	Framework     CanonicalFramework `json:"framework"`
	TotalControls int                `json:"total_controls"`
	Domains       []CanonicalDomain  `json:"domains"`
	Controls      []CanonicalControl `json:"controls"`
}

// CanonicalControlIndex provides fast lookup of canonical controls.
type CanonicalControlIndex struct {
	ByID          map[string]*CanonicalControl
	ByDomain      map[string][]*CanonicalControl
	BySeverity    map[string][]*CanonicalControl
	ByFramework   map[string][]*CanonicalControl // framework ref -> controls
	Domains       []CanonicalDomain
	Framework     CanonicalFramework
	AllControls   []*CanonicalControl
}

// LoadCanonicalControls loads the canonical control library from JSON.
func LoadCanonicalControls() (*CanonicalControlIndex, error) {
	data, err := readCanonicalControlsFile()
	if err != nil {
		return nil, err
	}

	var library CanonicalControlLibrary
	if err := json.Unmarshal(data, &library); err != nil {
		return nil, fmt.Errorf("failed to parse canonical controls: %w", err)
	}

	return buildCanonicalIndex(&library), nil
}

func readCanonicalControlsFile() ([]byte, error) {
	candidates := []string{
		"policies/canonical_controls_v1.json",
		"../policies/canonical_controls_v1.json",
		"../../policies/canonical_controls_v1.json",
	}

	_, filename, _, ok := runtime.Caller(0)
	if ok {
		srcDir := filepath.Dir(filename)
		candidates = append(candidates,
			filepath.Join(srcDir, "../../policies/canonical_controls_v1.json"),
		)
	}

	for _, p := range candidates {
		abs, err := filepath.Abs(p)
		if err != nil {
			continue
		}
		data, err := os.ReadFile(abs)
		if err == nil {
			return data, nil
		}
	}

	return nil, fmt.Errorf("canonical_controls_v1.json not found in any candidate path")
}

func buildCanonicalIndex(library *CanonicalControlLibrary) *CanonicalControlIndex {
	idx := &CanonicalControlIndex{
		ByID:        make(map[string]*CanonicalControl),
		ByDomain:    make(map[string][]*CanonicalControl),
		BySeverity:  make(map[string][]*CanonicalControl),
		ByFramework: make(map[string][]*CanonicalControl),
		Domains:     library.Domains,
		Framework:   library.Framework,
	}

	for i := range library.Controls {
		ctrl := &library.Controls[i]

		idx.ByID[ctrl.ControlID] = ctrl
		idx.ByDomain[ctrl.Domain] = append(idx.ByDomain[ctrl.Domain], ctrl)
		idx.BySeverity[ctrl.Severity] = append(idx.BySeverity[ctrl.Severity], ctrl)
		idx.AllControls = append(idx.AllControls, ctrl)

		for _, anchor := range ctrl.OpenAnchors {
			idx.ByFramework[anchor.Framework] = append(idx.ByFramework[anchor.Framework], ctrl)
		}
	}

	return idx
}

// GetControl returns a control by its ID (e.g. "AUTH-001").
func (idx *CanonicalControlIndex) GetControl(id string) (*CanonicalControl, bool) {
	ctrl, ok := idx.ByID[strings.ToUpper(id)]
	return ctrl, ok
}

// GetControlsByDomain returns all controls for a domain (e.g. "AUTH").
func (idx *CanonicalControlIndex) GetControlsByDomain(domain string) []*CanonicalControl {
	return idx.ByDomain[strings.ToUpper(domain)]
}

// GetControlsBySeverity returns all controls with a given severity.
func (idx *CanonicalControlIndex) GetControlsBySeverity(severity string) []*CanonicalControl {
	return idx.BySeverity[strings.ToLower(severity)]
}

// GetControlsByFramework returns all controls anchored to a framework (e.g. "OWASP ASVS").
func (idx *CanonicalControlIndex) GetControlsByFramework(framework string) []*CanonicalControl {
	return idx.ByFramework[framework]
}

// ValidateControlID checks if a control ID exists.
func (idx *CanonicalControlIndex) ValidateControlID(id string) bool {
	_, ok := idx.ByID[strings.ToUpper(id)]
	return ok
}
