// Command iace-audit runs static and runtime audits on the IACE pattern
// engine to find gaps without a ground-truth reference.
//
// Subcommands:
//
//	reachability  — Method A: which patterns can never fire given the library?
//	consistency   — Method B: do components cover their TypicalHazardCategories?
//	vocabulary    — Method C: which limits-form words are unknown to the dict?
//	echo          — Method D: which limits-form sentences have no hazard echo?
//	hierarchy     — Method E: which hazards lack design/protection/information?
package main

import (
	"encoding/json"
	"fmt"
	"os"

	"github.com/breakpilot/ai-compliance-sdk/internal/iace/audit"
)

func main() {
	if len(os.Args) < 2 {
		usage()
		os.Exit(2)
	}
	switch os.Args[1] {
	case "reachability":
		cmdReachability(os.Args[2:])
	case "consistency":
		cmdConsistency(os.Args[2:])
	case "vocabulary":
		cmdVocabulary(os.Args[2:])
	case "echo":
		cmdEcho(os.Args[2:])
	case "hierarchy":
		cmdHierarchy(os.Args[2:])
	case "propose":
		cmdPropose(os.Args[2:])
	default:
		usage()
		os.Exit(2)
	}
}

func usage() {
	fmt.Fprintln(os.Stderr, "Usage: iace-audit <reachability|consistency|vocabulary|echo|hierarchy|propose> [args]")
}

func cmdReachability(_ []string) {
	r := audit.RunReachability()
	printSummary(fmt.Sprintf("Method A — Pattern Reachability"), map[string]int{
		"total":            r.TotalPatterns,
		"reachable":        r.Reachable,
		"weakly_reachable": r.WeaklyReachable,
		"unreachable":      r.Unreachable,
		"universe_tags":    len(r.UniverseTags),
	})
	if len(r.UnreachablePatterns) > 0 {
		fmt.Println("\n## Unreachable patterns (top 30 by priority):")
		printPatternRows(r.UnreachablePatterns, 30)
	}
	if len(r.WeakPatterns) > 0 {
		fmt.Println("\n## Weakly reachable (top 20 by priority):")
		printPatternRows(r.WeakPatterns, 20)
	}
	writeJSON("audit-reports/reachability.json", r)
}

func cmdConsistency(_ []string) {
	r := audit.RunConsistency()
	printSummary("Method B — Component Self-Consistency", map[string]int{
		"total_components":   r.TotalComponents,
		"consistent":         r.Consistent,
		"incomplete":         r.Incomplete,
	})
	if len(r.IncompleteComponents) > 0 {
		fmt.Println("\n## Components missing tags for declared hazard categories:")
		for _, c := range r.IncompleteComponents {
			fmt.Printf("- %s (%s)\n", c.ComponentID, c.NameDE)
			for _, miss := range c.MissingForCategories {
				fmt.Printf("    %s: no pattern fires (suggest tags: %s)\n", miss.Category, joinFirst(miss.SuggestedTags, 5))
			}
		}
	}
	writeJSON("audit-reports/consistency.json", r)
}

func cmdVocabulary(args []string) {
	if len(args) < 1 {
		fmt.Fprintln(os.Stderr, "vocabulary: missing path to limits-form JSON")
		os.Exit(2)
	}
	data, err := os.ReadFile(args[0])
	must(err)
	var form map[string]any
	must(json.Unmarshal(data, &form))
	r := audit.RunVocabulary(form)
	printSummary("Method C — Vocabulary Diff", map[string]int{
		"unique_tokens":            r.UniqueTokens,
		"unknown_tokens":           len(r.UnknownTokens),
		"unknown_with_pattern_hit": len(r.SuggestedDictionaryEntries),
	})
	if len(r.SuggestedDictionaryEntries) > 0 {
		fmt.Println("\n## Suggested dictionary additions (token appears in pattern scenarios but not in dict):")
		for _, s := range r.SuggestedDictionaryEntries {
			fmt.Printf("- '%s' → seen in %d patterns. Examples: %s\n", s.Token, len(s.PatternIDs), joinFirst(s.PatternIDs, 5))
		}
	}
	writeJSON("audit-reports/vocabulary.json", r)
}

func cmdEcho(args []string) {
	if len(args) < 2 {
		fmt.Fprintln(os.Stderr, "echo: usage: iace-audit echo <limits-form.json> <hazards.json>")
		os.Exit(2)
	}
	limitsData, err := os.ReadFile(args[0])
	must(err)
	hazardsData, err := os.ReadFile(args[1])
	must(err)
	var form map[string]any
	must(json.Unmarshal(limitsData, &form))
	var hwrap struct {
		Hazards []map[string]any `json:"hazards"`
	}
	must(json.Unmarshal(hazardsData, &hwrap))
	r := audit.RunEcho(form, hwrap.Hazards)
	printSummary("Method D — Limits-Form Echo", map[string]int{
		"total_phrases":  r.TotalPhrases,
		"echoed":         r.Echoed,
		"orphaned":       r.Orphaned,
	})
	if len(r.OrphanedPhrases) > 0 {
		fmt.Println("\n## Orphaned phrases (no hazard echoes them):")
		for _, o := range r.OrphanedPhrases {
			fmt.Printf("- [%s] %s\n", o.Field, truncate(o.Phrase, 120))
		}
	}
	writeJSON("audit-reports/echo.json", r)
}

func cmdHierarchy(args []string) {
	if len(args) < 2 {
		fmt.Fprintln(os.Stderr, "hierarchy: usage: iace-audit hierarchy <hazards.json> <mitigations.json>")
		os.Exit(2)
	}
	hData, err := os.ReadFile(args[0])
	must(err)
	mData, err := os.ReadFile(args[1])
	must(err)
	var hwrap struct {
		Hazards []map[string]any `json:"hazards"`
	}
	must(json.Unmarshal(hData, &hwrap))
	var mwrap struct {
		Mitigations []map[string]any `json:"mitigations"`
	}
	must(json.Unmarshal(mData, &mwrap))
	r := audit.RunHierarchy(hwrap.Hazards, mwrap.Mitigations)
	printSummary("Method E — Hierarchy Completeness", map[string]int{
		"total_hazards":      r.TotalHazards,
		"complete":           r.Complete,
		"missing_design":     r.MissingDesign,
		"missing_protection": r.MissingProtection,
		"missing_info":       r.MissingInfo,
	})
	if len(r.IncompleteHazards) > 0 {
		fmt.Println("\n## Hazards with incomplete hierarchy:")
		for _, h := range r.IncompleteHazards {
			fmt.Printf("- [%s] %s — missing: %s\n", h.Category, truncate(h.Name, 70), joinFirst(h.MissingLevels, 3))
		}
	}
	writeJSON("audit-reports/hierarchy.json", r)
}

func printSummary(title string, kv map[string]int) {
	fmt.Println("=", title, "=")
	for k, v := range kv {
		fmt.Printf("  %-22s %d\n", k, v)
	}
}

func printPatternRows(rows []audit.ReachabilityResult, max int) {
	if max > len(rows) {
		max = len(rows)
	}
	for i := 0; i < max; i++ {
		r := rows[i]
		fmt.Printf("- %s (P%d) %s\n", r.PatternID, r.Priority, truncate(r.Name, 60))
		if len(r.UnreachableTags) > 0 {
			fmt.Printf("    missing tags: %s\n", joinFirst(r.UnreachableTags, 8))
		}
		for _, s := range r.FixSuggestions {
			fmt.Printf("    fix: %s\n", s)
		}
	}
}

func writeJSON(path string, v any) {
	_ = os.MkdirAll("audit-reports", 0o755)
	f, err := os.Create(path)
	if err != nil {
		fmt.Fprintln(os.Stderr, "warn: could not write report:", err)
		return
	}
	defer f.Close()
	enc := json.NewEncoder(f)
	enc.SetIndent("", "  ")
	_ = enc.Encode(v)
	fmt.Println("→ wrote", path)
}

func must(err error) {
	if err != nil {
		fmt.Fprintln(os.Stderr, err)
		os.Exit(1)
	}
}

func truncate(s string, n int) string {
	if len(s) <= n {
		return s
	}
	return s[:n] + "…"
}

func joinFirst(list []string, n int) string {
	if len(list) <= n {
		return join(list)
	}
	return join(list[:n]) + ", …"
}

func join(list []string) string {
	out := ""
	for i, s := range list {
		if i > 0 {
			out += ", "
		}
		out += s
	}
	return out
}
