// Package main seeds StemeDB with demo data using realistic agent identities.
//
// This program creates properly signed assertions for all 5 demo scenarios
// using pre-generated keypairs from demo/keys/agents.json.
//
// Usage:
//
//	go run ./cmd/demo-seed
//	go run ./cmd/demo-seed --api-url http://localhost:18180
//	go run ./cmd/demo-seed --keys-file ./demo/keys/agents.json --verbose
//
// The demo scenarios are:
//  1. Conflicting Claims - FDA vs Reddit on gastroparesis risk
//  2. Cascade Invalidation - Source retraction affecting assertions
//  3. Audit Trail - Agent query logging
//  4. Time Decay - Historical vs current market status
//  5. Trust & Safety - Quarantine and circuit breaker triggers
package main

import (
	"bytes"
	"context"
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/orchard9/stemedb-go/steme"
)

const (
	defaultBaseURL  = "http://localhost:18180"
	defaultKeysFile = "demo/keys/agents.json"
)

// DemoAgent represents a demo agent from agents.json.
type DemoAgent struct {
	Seed        string `json:"seed"`
	PublicKey   string `json:"public_key"`
	Tier        int    `json:"tier"`
	Description string `json:"description"`
	Sources     string `json:"sources"`
}

// AgentRegistry holds all demo agents.
type AgentRegistry struct {
	agents  map[string]DemoAgent
	signers map[string]*steme.Signer
}

func main() {
	// Parse flags
	apiURL := flag.String("api-url", "", "StemeDB API URL (default: $STEMEDB_API_URL or http://localhost:18180)")
	keysFile := flag.String("keys-file", "", "Path to agents.json (default: demo/keys/agents.json)")
	verbose := flag.Bool("verbose", false, "Enable verbose output")
	flag.Parse()

	// Resolve API URL
	url := *apiURL
	if url == "" {
		url = os.Getenv("STEMEDB_API_URL")
	}
	if url == "" {
		url = defaultBaseURL
	}

	// Resolve keys file path
	keysPath := *keysFile
	if keysPath == "" {
		// Try to find keys file relative to working directory
		keysPath = findKeysFile()
	}

	// Load agents
	registry, err := loadAgents(keysPath)
	if err != nil {
		log.Fatalf("Failed to load agents: %v", err)
	}

	ctx := context.Background()

	fmt.Println("=== StemeDB Demo Data Seeder ===")
	fmt.Println()
	fmt.Printf("API URL: %s\n", url)
	fmt.Printf("Keys File: %s\n", keysPath)
	fmt.Println()

	// Print agent mapping
	fmt.Println("Demo Agents:")
	for name, agent := range registry.agents {
		fmt.Printf("  %s (T%d) -> %s...\n", name, agent.Tier, agent.PublicKey[:16])
	}
	fmt.Println()

	// Verify server is running using FDA agent
	fdaSigner := registry.signers["fda:drug-label-ingestor"]
	client := steme.NewClient(url, fdaSigner)

	health, err := client.Health(ctx)
	if err != nil {
		log.Fatalf("Server not reachable at %s: %v\nStart the server with: cargo run --package stemedb-api", url, err)
	}
	fmt.Printf("Server Status: %s (v%s, %d assertions)\n", health.Status, health.Version, health.AssertionsCount)
	fmt.Println()

	// Register all demo sources BEFORE creating assertions
	fmt.Println("=== Registering Demo Sources ===")
	registered := 0
	for name, src := range Sources {
		err := registerSource(ctx, url, name, src)
		if err != nil {
			log.Printf("  Warning: %s: %v", name, err)
		} else {
			registered++
		}
	}
	fmt.Printf("  Registered %d sources\n\n", registered)

	// Populate all 5 demo scenarios
	demo1Hashes := populateDemo1ConflictingClaims(ctx, registry, url, *verbose)
	sourceHash := populateDemo2SourceRetraction(ctx, registry, url, *verbose)
	agentID := populateDemo3AuditTrail(ctx, registry, url, *verbose)
	populateDemo4TimeDecay(ctx, registry, url, *verbose)
	populateDemo5TrustSafety(ctx, registry, url, *verbose)

	// P2.2: Populate Drug Conflict Scenarios (150+ assertions across 3 drugs)
	populateDrugConflicts(ctx, registry, url)

	// Wait for ingestion
	fmt.Println("Waiting for async ingestion...")
	time.Sleep(1 * time.Second)

	// Print all demo curl commands
	fmt.Println()
	fmt.Println("========================================")
	fmt.Println("=== DEMO CURL COMMANDS ===")
	fmt.Println("========================================")
	fmt.Println()

	printDemo1Commands(demo1Hashes)
	printDemo2Commands(sourceHash)
	printDemo3Commands(agentID)
	printDemo4Commands()
	printDemo5Commands()
	printConflictDemoCommands()
	printHistoricalDemoCommands()
	printCascadeDemoCommands()

	fmt.Println("========================================")
	fmt.Println("=== AGENT ID REFERENCE ===")
	fmt.Println("========================================")
	fmt.Println()
	fmt.Println("When viewing data in the dashboard, use this mapping:")
	fmt.Println()
	for name, agent := range registry.agents {
		fmt.Printf("  %s...\n    -> %s (T%d)\n", agent.PublicKey[:24], name, agent.Tier)
	}
	fmt.Println()
}

// findKeysFile locates the agents.json file.
func findKeysFile() string {
	// Try current directory
	if _, err := os.Stat(defaultKeysFile); err == nil {
		return defaultKeysFile
	}

	// Try one level up (if running from cmd/demo-seed)
	upPath := filepath.Join("..", "..", defaultKeysFile)
	if _, err := os.Stat(upPath); err == nil {
		return upPath
	}

	// Default to standard path
	return defaultKeysFile
}

// loadAgents loads agent configurations from agents.json.
func loadAgents(path string) (*AgentRegistry, error) {
	data, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read %s: %w", path, err)
	}

	var agents map[string]DemoAgent
	if err := json.Unmarshal(data, &agents); err != nil {
		return nil, fmt.Errorf("failed to parse %s: %w", path, err)
	}

	signers := make(map[string]*steme.Signer)
	for name, agent := range agents {
		signer, err := steme.NewSignerFromHex(agent.Seed)
		if err != nil {
			return nil, fmt.Errorf("failed to create signer for %s: %w", name, err)
		}
		signers[name] = signer
	}

	return &AgentRegistry{
		agents:  agents,
		signers: signers,
	}, nil
}

// Demo1Hashes holds the assertion hashes for Demo 1.
type Demo1Hashes struct {
	FDA       string
	Anecdotal string
}

// populateDemo1ConflictingClaims creates conflicting assertions about gastroparesis risk.
func populateDemo1ConflictingClaims(ctx context.Context, registry *AgentRegistry, baseURL string, _ bool) Demo1Hashes {
	fmt.Println("=== Demo 1: Populating Conflicting Claims ===")

	var hashes Demo1Hashes

	// FDA/Regulatory (Tier 0): Low risk from clinical trials
	fdaSigner := registry.signers["fda:drug-label-ingestor"]
	fdaClient := steme.NewClient(baseURL, fdaSigner)

	fdaAssertion := steme.NewAssertion("semaglutide:gastroparesis_risk", "risk_level").
		WithText("Low incidence in clinical trials (0.2%)").
		WithConfidence(0.95).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassRegulatory).
		WithSourceHash("0da1111111111111111111111111111111111111111111111111111111110da1").
		Build()

	hash, err := fdaClient.Assert(ctx, fdaAssertion)
	if err != nil {
		log.Printf("Warning: Failed to create FDA assertion: %v", err)
	} else {
		hashes.FDA = hash
		fmt.Printf("  fda:drug-label-ingestor (T0): %s\n", hash[:16]+"...")
	}

	// Anecdotal (Tier 5): High patient-reported incidence
	redditSigner := registry.signers["reddit:health-discussion-scraper"]
	redditClient := steme.NewClient(baseURL, redditSigner)

	anecdotalAssertion := steme.NewAssertion("semaglutide:gastroparesis_risk", "risk_level").
		WithText("High patient-reported incidence (see r/Ozempic)").
		WithConfidence(0.70).
		WithLifecycle(steme.LifecycleProposed).
		WithSourceClass(steme.SourceClassAnecdotal).
		WithSourceHash("5555555555555555555555555555555555555555555555555555555555555555").
		Build()

	hash, err = redditClient.Assert(ctx, anecdotalAssertion)
	if err != nil {
		log.Printf("Warning: Failed to create anecdotal assertion: %v", err)
	} else {
		hashes.Anecdotal = hash
		fmt.Printf("  reddit:health-discussion-scraper (T5): %s\n", hash[:16]+"...")
	}

	fmt.Println()
	return hashes
}

// populateDemo2SourceRetraction creates assertions for cascade invalidation demo.
func populateDemo2SourceRetraction(ctx context.Context, registry *AgentRegistry, baseURL string, _ bool) string {
	fmt.Println("=== Demo 2: Populating Source for Retraction Demo ===")

	// Use PubMed agent for clinical trial data
	pubmedSigner := registry.signers["pubmed:abstract-indexer"]
	client := steme.NewClient(baseURL, pubmedSigner)

	// Use the actual hash from the registered CARDIOVASC_MEGA_TRIAL source
	// This ensures "Preview Impact" on the source page will find these assertions
	sourceHash := Sources["CARDIOVASC_MEGA_TRIAL"].Hash()

	fmt.Printf("  Source (NEJM Study): %s\n", sourceHash[:16]+"...")

	// Create assertions citing this source
	assertion1 := steme.NewAssertion("GLP1_Agonists", "cardiovascular_benefit").
		WithBoolean(true).
		WithConfidence(0.92).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassClinical).
		WithSourceHash(sourceHash).
		Build()

	hash, err := client.Assert(ctx, assertion1)
	if err != nil {
		log.Printf("Warning: Failed to create CV benefit assertion: %v", err)
	} else {
		fmt.Printf("  pubmed:abstract-indexer (T1): %s (CV benefit)\n", hash[:16]+"...")
	}

	assertion2 := steme.NewAssertion("GLP1_Agonists", "mortality_reduction").
		WithText("20% reduction in cardiovascular mortality").
		WithConfidence(0.88).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassClinical).
		WithSourceHash(sourceHash).
		Build()

	hash, err = client.Assert(ctx, assertion2)
	if err != nil {
		log.Printf("Warning: Failed to create mortality assertion: %v", err)
	} else {
		fmt.Printf("  pubmed:abstract-indexer (T1): %s (mortality reduction)\n", hash[:16]+"...")
	}

	fmt.Println()
	return sourceHash
}

// populateDemo3AuditTrail creates assertions and queries to generate audit trail data.
func populateDemo3AuditTrail(ctx context.Context, registry *AgentRegistry, baseURL string, _ bool) string {
	fmt.Println("=== Demo 3: Populating Audit Trail Data ===")

	// Use FDA agent for regulatory assertions
	fdaSigner := registry.signers["fda:drug-label-ingestor"]
	fdaClient := steme.NewClient(baseURL, fdaSigner)
	agentID := fdaSigner.PublicKey()

	// Create assertion about approved indication
	indication := steme.NewAssertion("semaglutide", "approved_indication").
		WithText("Type 2 diabetes mellitus").
		WithConfidence(1.0).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassRegulatory).
		WithSourceHash("1001111111111111111111111111111111111111111111111111111111111001").
		Build()

	hash, err := fdaClient.Assert(ctx, indication)
	if err != nil {
		log.Printf("Warning: Failed to create indication assertion: %v", err)
	} else {
		fmt.Printf("  fda:drug-label-ingestor (T0): %s (indication)\n", hash[:16]+"...")
	}

	// Make queries to populate audit logs
	_, err = fdaClient.Query(ctx, steme.QueryParams{
		Subject:   ptr("semaglutide"),
		Predicate: ptr("approved_indication"),
		Lens:      lensPtr(steme.LensAuthority),
	})
	if err != nil {
		log.Printf("Warning: Query failed: %v", err)
	} else {
		fmt.Printf("  Query logged for fda:drug-label-ingestor: %s...\n", agentID[:16])
	}

	// Make a skeptic query from the internal reviewer
	internalSigner := registry.signers["internal:clinical-ops-reviewer"]
	internalClient := steme.NewClient(baseURL, internalSigner)

	_, err = internalClient.Skeptic(ctx, steme.SkepticQueryParams{
		Subject:   "semaglutide:gastroparesis_risk",
		Predicate: "risk_level",
	})
	if err != nil {
		log.Printf("Warning: Skeptic query failed: %v", err)
	} else {
		internalID := internalSigner.PublicKey()
		fmt.Printf("  Skeptic query logged for internal:clinical-ops-reviewer: %s...\n", internalID[:16])
	}

	fmt.Println()
	return agentID
}

// populateDemo4TimeDecay creates assertions with different timestamps.
func populateDemo4TimeDecay(ctx context.Context, registry *AgentRegistry, baseURL string, _ bool) {
	fmt.Println("=== Demo 4: Populating Time-Based Assertions ===")

	// Use FDA agent for market status
	fdaSigner := registry.signers["fda:drug-label-ingestor"]
	client := steme.NewClient(baseURL, fdaSigner)

	// Current market status
	current := steme.NewAssertion("semaglutide", "market_status").
		WithText("FDA approved for weight management (2024)").
		WithConfidence(1.0).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassRegulatory).
		WithSourceHash("11ae111111111111111111111111111111111111111111111111111111ae1111").
		Build()

	hash, err := client.Assert(ctx, current)
	if err != nil {
		log.Printf("Warning: Failed to create current market status: %v", err)
	} else {
		fmt.Printf("  fda:drug-label-ingestor (T0): %s (current status)\n", hash[:16]+"...")
	}

	// Historical status (deprecated)
	historical := steme.NewAssertion("semaglutide", "market_status").
		WithText("FDA approved for diabetes only (2017)").
		WithConfidence(0.85).
		WithLifecycle(steme.LifecycleDeprecated).
		WithSourceClass(steme.SourceClassRegulatory).
		WithSourceHash("11ae222222222222222222222222222222222222222222222222222222ae2222").
		Build()

	hash, err = client.Assert(ctx, historical)
	if err != nil {
		log.Printf("Warning: Failed to create historical market status: %v", err)
	} else {
		fmt.Printf("  fda:drug-label-ingestor (T0): %s (historical status)\n", hash[:16]+"...")
	}

	fmt.Println()
}

// populateDemo5TrustSafety creates data that triggers quarantine and circuit breaker demos.
func populateDemo5TrustSafety(ctx context.Context, _ *AgentRegistry, baseURL string, _ bool) {
	fmt.Println("=== Demo 5: Populating Trust & Safety Triggers ===")

	// Create multiple untrusted agents for different scenarios
	fmt.Println("  Creating suspicious assertions for quarantine demo...")

	// Scenario 1: Untrusted high-confidence agent
	untrustedSigner, err := steme.GenerateSigner()
	if err != nil {
		log.Printf("Warning: Failed to generate untrusted signer: %v", err)
		return
	}
	untrustedClient := steme.NewClient(baseURL, untrustedSigner)

	// This assertion from a new agent with high confidence should trigger quarantine
	suspicious := steme.NewAssertion("miracle_drug", "cures_everything").
		WithBoolean(true).
		WithConfidence(0.99). // Very high confidence from untrusted agent
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassAnecdotal).
		WithSourceHash("5a00111111111111111111111111111111111111111111111111111111005a11").
		Build()

	hash, err := untrustedClient.Assert(ctx, suspicious)
	if err != nil {
		fmt.Printf("  [Quarantine] High-confidence untrusted: %v\n", err)
	} else {
		fmt.Printf("  [Quarantine] High-confidence untrusted: %s\n", hash[:16]+"...")
	}

	// Scenario 2: Low-quality content (short, low entropy)
	lowQuality := steme.NewAssertion("test", "x").
		WithText("a"). // Very short, low quality
		WithConfidence(0.5).
		WithLifecycle(steme.LifecycleProposed).
		WithSourceClass(steme.SourceClassAnecdotal).
		WithSourceHash("5a00222222222222222222222222222222222222222222222222222222005a22").
		Build()

	hash, err = untrustedClient.Assert(ctx, lowQuality)
	if err != nil {
		fmt.Printf("  [Quarantine] Low-quality content: %v\n", err)
	} else {
		fmt.Printf("  [Quarantine] Low-quality content: %s\n", hash[:16]+"...")
	}

	// Scenario 3: Pattern match (spam-like keywords)
	spammy := steme.NewAssertion("free_bitcoin", "get_rich_quick").
		WithText("Click here for free money! Limited time offer!").
		WithConfidence(0.95).
		WithLifecycle(steme.LifecycleApproved).
		WithSourceClass(steme.SourceClassAnecdotal).
		WithSourceHash("5a00333333333333333333333333333333333333333333333333333333005a33").
		Build()

	hash, err = untrustedClient.Assert(ctx, spammy)
	if err != nil {
		fmt.Printf("  [Quarantine] Spam pattern: %v\n", err)
	} else {
		fmt.Printf("  [Quarantine] Spam pattern: %s\n", hash[:16]+"...")
	}

	fmt.Printf("  Untrusted agent ID: %s...\n", untrustedSigner.PublicKey()[:16])

	// Trigger circuit breaker by sending malformed requests
	fmt.Println("\n  Triggering circuit breaker with invalid requests...")
	triggerCircuitBreaker(baseURL)

	fmt.Println()
}

// triggerCircuitBreaker sends malformed requests to trip the circuit breaker.
func triggerCircuitBreaker(baseURL string) {
	// Send multiple invalid assertion requests to trigger failures
	for i := 0; i < 5; i++ {
		req, err := http.NewRequest("POST", baseURL+"/v1/assert",
			bytes.NewReader([]byte(`{"invalid": "payload", "missing_required_fields": true}`)))
		if err != nil {
			continue
		}
		req.Header.Set("Content-Type", "application/json")
		// Add a fake agent ID header (invalid signature will cause failure)
		req.Header.Set("X-Agent-Id", fmt.Sprintf("bad_agent_%d", i))

		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			fmt.Printf("  [Circuit] Request %d failed: %v\n", i+1, err)
			continue
		}
		resp.Body.Close()
		fmt.Printf("  [Circuit] Request %d: HTTP %d\n", i+1, resp.StatusCode)
	}
}

// ptr returns a pointer to a string.
func ptr(s string) *string {
	return &s
}

// lensPtr returns a pointer to a Lens.
func lensPtr(l steme.Lens) *steme.Lens {
	return &l
}

// printDemo1Commands prints curl commands for Demo 1.
func printDemo1Commands(_ Demo1Hashes) {
	fmt.Println("--- Demo 1: Contradictions Visible ---")
	fmt.Println()
	fmt.Println("# Skeptic: See all competing claims")
	fmt.Println(`curl -s "http://localhost:18180/v1/skeptic?subject=semaglutide:gastroparesis_risk&predicate=risk_level" | jq`)
	fmt.Println()
	fmt.Println("# Layered: See per-tier consensus")
	fmt.Println(`curl -s "http://localhost:18180/v1/layered?subject=semaglutide:gastroparesis_risk&predicate=risk_level" | jq`)
	fmt.Println()
	fmt.Println("# Expected: conflict_score > 0.5")
	fmt.Println("# fda:drug-label-ingestor (T0) wins overall, but reddit:health-discussion-scraper (T5) disagrees")
	fmt.Println()
}

// printDemo2Commands prints curl commands for Demo 2.
func printDemo2Commands(sourceHash string) {
	fmt.Println("--- Demo 2: Cascade Invalidation ---")
	fmt.Println()
	fmt.Println("# Query assertions citing the NEJM source")
	fmt.Println(`curl -s "http://localhost:18180/v1/query?subject=GLP1_Agonists" | jq`)
	fmt.Println()
	fmt.Println("# Expected: Two assertions from pubmed:abstract-indexer (cardiovascular_benefit, mortality_reduction)")
	fmt.Printf("# Both cite source_hash: %s\n", sourceHash)
	fmt.Println()
}

// printDemo3Commands prints curl commands for Demo 3.
func printDemo3Commands(agentID string) {
	now := time.Now().Unix()
	from := now - 3600 // 1 hour ago

	fmt.Println("--- Demo 3: Full Audit Trail ---")
	fmt.Println()
	fmt.Println("# List recent query audits")
	fmt.Println(`curl -s "http://localhost:18180/v1/audit/queries?limit=10" | jq`)
	fmt.Println()
	fmt.Println("# Trace fda:drug-label-ingestor decisions")
	fmt.Printf("curl -s \"http://localhost:18180/v1/trace?agent_id=%s&from=%d&limit=50\" | jq\n", agentID, from)
	fmt.Println()
	fmt.Println("# Filter trace by subject pattern")
	fmt.Printf("curl -s \"http://localhost:18180/v1/trace?agent_id=%s&from=%d&subject=semaglutide*\" | jq\n", agentID, from)
	fmt.Println()
}

// printDemo4Commands prints curl commands for Demo 4.
func printDemo4Commands() {
	now := time.Now().Unix()
	sixMonthsAgo := now - (180 * 24 * 60 * 60)

	fmt.Println("--- Demo 4: Time Decay ---")
	fmt.Println()
	fmt.Println("# Current state with Recency lens")
	fmt.Println(`curl -s "http://localhost:18180/v1/query?subject=semaglutide&predicate=market_status&lens=Recency" | jq`)
	fmt.Println()
	fmt.Println("# Historical state (point-in-time query)")
	fmt.Printf("curl -s \"http://localhost:18180/v1/query?subject=semaglutide&predicate=market_status&lens=Recency&as_of=%d\" | jq\n", sixMonthsAgo)
	fmt.Println()
}

// printDemo5Commands prints curl commands for Demo 5.
func printDemo5Commands() {
	fmt.Println("--- Demo 5: Trust & Safety ---")
	fmt.Println()
	fmt.Println("# Check quarantine queue")
	fmt.Println(`curl -s "http://localhost:18180/v1/admin/quarantine?limit=10" | jq`)
	fmt.Println()
	fmt.Println("# Check tripped circuit breakers")
	fmt.Println(`curl -s "http://localhost:18180/v1/admin/circuit-breakers/tripped" | jq`)
	fmt.Println()
	fmt.Println("# To approve a quarantined assertion:")
	fmt.Println("# curl -X POST \"http://localhost:18180/v1/admin/quarantine/{hash}/approve\"")
	fmt.Println()
}

// sourceClassTier maps SourceClass to tier number.
var sourceClassTier = map[steme.SourceClass]int{
	steme.SourceClassRegulatory:    0,
	steme.SourceClassClinical:      1,
	steme.SourceClassObservational: 2,
	steme.SourceClassExpert:        3,
	steme.SourceClassCommunity:     4,
	steme.SourceClassAnecdotal:     5,
}

// registerSource registers a source in the source registry.
// Uses raw HTTP since steme.Client.doJSON is private.
func registerSource(ctx context.Context, baseURL, name string, src Source) error {
	tier := sourceClassTier[src.Class]
	payload := map[string]interface{}{
		"hash":       src.Hash(),
		"label":      src.Description,
		"tier":       tier,
		"tier_label": string(src.Class),
		"url":        src.URL,
		"notes":      fmt.Sprintf("Demo source: %s (%s)", name, src.ID),
	}

	jsonBytes, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("failed to marshal: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "POST", baseURL+"/v1/sources", bytes.NewReader(jsonBytes))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/json")

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	// 201 Created = success, 409 Conflict = already exists (OK)
	if resp.StatusCode == 201 || resp.StatusCode == 409 {
		return nil
	}

	body, _ := io.ReadAll(resp.Body)
	return fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
}
