package resilience

import (
	"encoding/json"
	"os"
	"path/filepath"
	"strings"
	"testing"
)

func TestObservabilityDashboardCoversBankingDomains(t *testing.T) {
	t.Parallel()

	raw := readRepoFile(t, "deploy", "observability", "grafana-banking-overview.json")
	var dashboard map[string]any
	if err := json.Unmarshal([]byte(raw), &dashboard); err != nil {
		t.Fatalf("dashboard JSON is invalid: %v", err)
	}

	requiredTitles := []string{
		"API Health",
		"DB Health",
		"Ledger Postings",
		"Transfers",
		"Settlements",
		"Cards",
		"KYC And AML",
		"FX",
		"Auth",
		"Provider Calls",
	}
	for _, title := range requiredTitles {
		if !strings.Contains(raw, title) {
			t.Fatalf("dashboard is missing panel/title containing %q", title)
		}
	}
}

func TestAlertRulesIncludeRoutingSeverityAndRunbooks(t *testing.T) {
	t.Parallel()

	raw := readRepoFile(t, "deploy", "observability", "prometheus-rules.yml")
	required := []string{
		"severity: critical",
		"severity: warning",
		"route: api-oncall",
		"route: security-oncall",
		"route: compliance-oncall",
		"route: finance-oncall",
		"runbook: docs/runbooks/provider-outage.md",
		"runbook: docs/runbooks/ledger-imbalance.md",
		"runbook: docs/runbooks/settlement-break.md",
		"runbook: docs/runbooks/auth-attack.md",
		"runbook: docs/runbooks/card-webhook-storm.md",
	}
	for _, token := range required {
		if !strings.Contains(raw, token) {
			t.Fatalf("alert rules missing %q", token)
		}
	}
}

func TestIncidentRunbooksAndResiliencePlansExist(t *testing.T) {
	t.Parallel()

	files := map[string][]string{
		filepath.Join("docs", "runbooks", "provider-outage.md"):                       {"Owner:", "Triggers", "Evidence", "Communications"},
		filepath.Join("docs", "runbooks", "settlement-break.md"):                      {"Owner:", "Triggers", "Evidence", "Communications"},
		filepath.Join("docs", "runbooks", "ledger-imbalance.md"):                      {"Owner:", "Triggers", "Recovery", "Communications"},
		filepath.Join("docs", "runbooks", "deploy.md"):                                {"Owner:", "Entry Criteria", "Deployment", "Abort And Rollback Criteria", "Closure Evidence"},
		filepath.Join("docs", "runbooks", "rollback.md"):                              {"Owner:", "Triggers", "API Rollback", "Frontend Rollback", "Database And Migration Decision"},
		filepath.Join("docs", "runbooks", "reconciliation-break.md"):                  {"Owner:", "Triggers", "Diagnose By Break Type", "Resolution Paths", "Closure Evidence"},
		filepath.Join("docs", "runbooks", "data-breach.md"):                           {"Owner:", "Triggers", "Notification", "Recovery"},
		filepath.Join("docs", "runbooks", "auth-attack.md"):                           {"Owner:", "Triggers", "Recovery"},
		filepath.Join("docs", "runbooks", "card-webhook-storm.md"):                    {"Owner:", "Triggers", "Recovery", "Communications"},
		filepath.Join("docs", "runbooks", "major-incident-notification.md"):           {"Owner:", "Major Incident Thresholds", "Notification Matrix", "Required Closure Evidence"},
		filepath.Join("docs", "runbooks", "business-continuity-disaster-recovery.md"): {"Owner:", "Quarterly DR Test", "Continuity Actions", "Closure"},
		filepath.Join("docs", "resilience", "chaos-failure-injection.md"):             {"Owner:", "Required Scenarios", "Provider outage", "DB failover", "Evidence"},
	}
	for file, tokens := range files {
		raw := readRepoFile(t, strings.Split(file, string(filepath.Separator))...)
		for _, token := range tokens {
			if !strings.Contains(raw, token) {
				t.Fatalf("%s missing %q", file, token)
			}
		}
	}
}

func TestArchitectureThreatModelAndOperatingProceduresExist(t *testing.T) {
	t.Parallel()

	files := map[string][]string{
		filepath.Join("docs", "architecture-and-data-flows.md"): {
			"System Context",
			"Runtime And Network Boundaries",
			"Sources Of Truth",
			"Transfer And Ledger Flow",
			"Provider Webhook Flow",
		},
		filepath.Join("docs", "threat-model.md"): {
			"Authentication Threats",
			"Money Movement And Ledger Threats",
			"Webhook Threats",
			"Admin And Backoffice Threats",
			"Card Threats",
			"Crypto And Stablecoin Threats",
			"FX Threats",
		},
		filepath.Join("docs", "compliance-operations-procedures.md"): {
			"KYC And Identity Verification",
			"Sanctions, PEP, And Adverse Media",
			"AML Transaction Monitoring",
			"Fraud Operations",
			"Suspicious Activity And SAR/STR Escalation",
			"Customer Complaints",
		},
		filepath.Join("docs", "support-playbooks.md"): {
			"Non-Negotiable Safety Rules",
			"Payment Pending, Failed, Missing, Or Duplicated",
			"Balance Or Ledger Discrepancy",
			"Complaints And Vulnerable Customers",
		},
		filepath.Join("docs", "operations-onboarding-training.md"): {
			"Common Core",
			"Role Tracks",
			"Training Sequence",
			"Access Provisioning Checklist",
			"Recertification And Quality Monitoring",
		},
		filepath.Join("docs", "provider-integrations.md"): {
			"Runtime Modes",
			"Outbound Call Controls",
			"Webhook Controls",
			"Admin Operations",
			"Required Evidence",
			"Real Adapter Rule",
		},
		filepath.Join("docs", "licensing-legal-market-access.md"): {
			"Regulated Activity Decision Matrix",
			"Operating Model",
			"Legal Memo Evidence Pack",
			"Restricted Terms",
			"Provider Contracts",
			"Safeguarding",
			"Customer Policies",
			"Jurisdiction Blocking",
		},
		filepath.Join("docs", "go-live-gates.md"): {
			"Gate A: Staging Launch",
			"Gate B: Closed Beta With Test Money Only",
			"Gate C: Real-Money Production",
			"Admin Operations",
			"Status Rules",
			"Closure Evidence",
		},
	}

	for file, tokens := range files {
		raw := readRepoFile(t, strings.Split(file, string(filepath.Separator))...)
		for _, token := range tokens {
			if !strings.Contains(raw, token) {
				t.Fatalf("%s missing %q", file, token)
			}
		}
	}
}

func TestMajorIncidentNotificationCoversRegulatorsProvidersAndCustomers(t *testing.T) {
	t.Parallel()

	raw := readRepoFile(t, "docs", "runbooks", "major-incident-notification.md")
	for _, token := range []string{"Regulators", "Providers", "Customers", "DORA", "Required Closure Evidence"} {
		if !strings.Contains(raw, token) {
			t.Fatalf("major incident process missing %q", token)
		}
	}
}

func readRepoFile(t *testing.T, parts ...string) string {
	t.Helper()
	path := filepath.Join(append([]string{"..", "..", ".."}, parts...)...)
	raw, err := os.ReadFile(path)
	if err != nil {
		t.Fatalf("read %s: %v", path, err)
	}
	return string(raw)
}
