package handler

import (
	"html"
	"os"
	"strings"

	"github.com/gofiber/fiber/v2"
)

type RPManifestHandler struct{}

const rpObjectLookupMermaid = `flowchart TD
    A[RP request] --> B{obj_id supplied?}
    B -->|yes| C[Normalize object type and obj_id]
    B -->|no| D{Route has client_id?}
    D -->|yes| E[obj_id = RelyingParty:<client_id>]
    D -->|no| F{Route has tenant_id?}
    F -->|yes| G[obj_id = Tenant:<tenant_id>]
    F -->|no| H[Reject: explicit obj_id required]
    C --> I[Check Keto relation]
    E --> I
    G --> I
    I --> J{allowed?}
    J -->|yes| K[Inject trusted Baron headers]
    J -->|no| L[Reject request]
    K --> M[Write audit with obj_id, relation, client_id, X-Request-Id]`

const rpExternalKeyMermaid = `flowchart TD
    A[User authenticates through Baron SSO] --> B[Baron resolves internal identity]
    B --> C[Baron derives or loads Baron-issued alias]
    C --> D[Baron injects X-Baron-External-Key]
    D --> E[Baron injects X-Baron-Subject]
    E --> I[RP receives trusted headers from Baron gateway]
    I --> F[RP upserts local user with provider + X-Baron-External-Key]
    F --> G[RP stores the full external key as opaque value]
    G --> H[RP never parses or stores raw kratos_identity_id]`

func NewRPManifestHandler() *RPManifestHandler {
	return &RPManifestHandler{}
}

func (h *RPManifestHandler) GetJSON(c *fiber.Ctx) error {
	c.Set(fiber.HeaderCacheControl, "public, max-age=300")
	return c.JSON(buildRPManifest(c))
}

func (h *RPManifestHandler) GetSchema(c *fiber.Ctx) error {
	c.Set(fiber.HeaderCacheControl, "public, max-age=300")
	return c.JSON(rpManifestSchema())
}

func (h *RPManifestHandler) GetHTML(c *fiber.Ctx) error {
	manifest := buildRPManifest(c)
	issuer, _ := manifest["issuer"].(string)
	c.Set(fiber.HeaderCacheControl, "public, max-age=300")
	c.Type("html", "utf-8")
	return c.SendString(`<!doctype html>
<html lang="ko">
<head>
  <meta charset="utf-8">
  <title>Baron RP IAM Manifest</title>
  <style>
    body { font-family: system-ui, sans-serif; margin: 2rem; line-height: 1.6; max-width: 920px; }
    code, pre { background: #f5f5f5; border-radius: 4px; padding: .1rem .3rem; }
    pre { padding: 1rem; overflow: auto; }
    table { border-collapse: collapse; width: 100%; }
    th, td { border: 1px solid #ddd; padding: .5rem; text-align: left; }
  </style>
</head>
<body>
  <h1>Baron RP IAM Manifest</h1>
  <p>외부 RP가 Baron SSO/Ory Stack/Keto 기반 공용 IAM을 연동하기 위한 공개 규격입니다.</p>
  <ul>
    <li>Machine-readable manifest: <a href="/.well-known/baron-rp-manifest.json">/.well-known/baron-rp-manifest.json</a></li>
    <li>JSON schema: <a href="/.well-known/baron-rp-manifest.schema.json">/.well-known/baron-rp-manifest.schema.json</a></li>
  </ul>
  <h2>Issuer</h2>
  <pre>` + html.EscapeString(issuer) + `</pre>
  <h2>Identity Contract</h2>
  <table>
    <tr><th>용도</th><th>Header</th><th>정책</th></tr>
    <tr><td>Keto subject</td><td><code>X-Baron-Subject</code></td><td><code>User:&lt;baron_identity_id&gt;</code> 전체 문자열을 opaque subject로 취급합니다.</td></tr>
    <tr><td>RP upsert key</td><td><code>X-Baron-External-Key</code></td><td>Baron-issued alias입니다. RP가 만들거나 제출하지 않고, Baron이 주입한 전체 문자열을 local user external key로 저장합니다.</td></tr>
    <tr><td>RP client</td><td><code>X-Baron-Client-ID</code></td><td>현재 접근 중인 RP client id입니다.</td></tr>
  </table>
  <h2>External Key Flow</h2>
  <p><code>X-Baron-External-Key</code>는 RP 입력값이 아니라 Baron이 인증된 subject에서 발급/조회해 주입하는 opaque alias입니다. RP upserts local user from the Baron-issued alias.</p>
  <pre>` + "```mermaid\n" + html.EscapeString(rpExternalKeyMermaid) + "\n```" + `</pre>
  <h2>Object Lookup</h2>
  <pre>check(User:abc, viewers, RelyingParty:&lt;client_id&gt;)
check(User:abc, members, Tenant:&lt;tenant_id&gt;)
check(User:abc, viewers, Resource:&lt;resource_type&gt;:&lt;resource_id&gt;)</pre>
  <h2>audit_contract</h2>
  <p>권한과 설정을 변경하는 command는 sync audit write에 실패하면 요청도 실패해야 합니다. Read audit은 allowlist된 조회에 한해 best effort로 취급합니다.</p>
  <pre>{
  "mutating_command_mode": "fail_closed_sync",
  "missing_audit_sink_behavior": "reject_mutation",
  "correlation_header": "X-Request-Id"
}</pre>
  <h2>Object Lookup Flow</h2>
  <pre>` + "```mermaid\n" + html.EscapeString(rpObjectLookupMermaid) + "\n```" + `</pre>
</body>
</html>`)
}

func buildRPManifest(c *fiber.Ctx) map[string]any {
	issuer := resolvePublicRequestBaseURL(c, os.Getenv("BACKEND_PUBLIC_URL"))
	if issuer == "" {
		issuer = strings.TrimRight(os.Getenv("USERFRONT_URL"), "/")
	}
	if issuer == "" {
		issuer = "https://sso.hmac.kr"
	}
	issuer = strings.TrimRight(issuer, "/")

	return map[string]any{
		"version": "2026-05-11",
		"issuer":  issuer,
		"oidc": map[string]any{
			"discovery_url":   issuer + "/.well-known/openid-configuration",
			"jwks_url":        issuer + "/.well-known/jwks.json",
			"supported_flows": []string{"authorization_code_pkce"},
			"required_scopes": []string{"openid", "profile", "email"},
		},
		"iam": map[string]any{
			"authorization_engine": "ory-keto",
			"subject_format":       "User:<baron_identity_id>",
			"target_object_patterns": []string{
				"RelyingParty:<client_id>",
				"Tenant:<tenant_id>",
				"Resource:<resource_type>:<resource_id>",
			},
			"supported_relations": []string{
				"admins",
				"users",
				"viewers",
				"operators",
				"members",
				"owners",
				"editors",
			},
		},
		"identity_contract": map[string]any{
			"subject_header":                   "X-Baron-Subject",
			"external_key_header":              "X-Baron-External-Key",
			"external_key_is_opaque":           true,
			"external_key_issuer":              "baron",
			"external_key_delivery":            "baron_injected_header",
			"external_key_lifecycle":           "issued_or_loaded_after_successful_authentication_before_rp_request",
			"rp_supplied_external_key_allowed": false,
			"rp_user_upsert_source":            "rp_must_upsert_from_header_value",
			"raw_kratos_identity_id_exposed":   false,
			"rp_user_upsert_key":               "provider + external_key",
			"email_is_stable_primary_key":      false,
			"initial_external_key_expression":  "X-Baron-External-Key",
			"fallback_to_subject_allowed":      false,
		},
		"trusted_headers": map[string]any{
			"subject":      "X-Baron-Subject",
			"external_key": "X-Baron-External-Key",
			"email":        "X-Baron-Email",
			"tenant":       "X-Baron-Tenant",
			"relations":    "X-Baron-Relations",
			"client_id":    "X-Baron-Client-ID",
		},
		"object_lookup": map[string]any{
			"rp_level": map[string]any{
				"object":    "RelyingParty:<client_id>",
				"relations": []string{"viewers", "users", "operators", "admins"},
				"example":   "check(User:abc, viewers, RelyingParty:mh-dashboard)",
			},
			"tenant_level": map[string]any{
				"object":    "Tenant:<tenant_id>",
				"relations": []string{"members", "admins", "owners"},
				"example":   "check(User:abc, members, Tenant:9caf62e1-297d-4e8f-870b-61780998bbe)",
			},
			"resource_level": map[string]any{
				"object":    "Resource:<resource_type>:<resource_id>",
				"relations": []string{"viewers", "editors", "owners"},
				"example":   "check(User:abc, viewers, Resource:dashboard:mh-monthly-2026-05)",
			},
			"recommended_order": []string{
				"authenticated",
				"rp_level",
				"tenant_or_resource_level",
				"trusted_header_injection",
			},
		},
		"object_lookup_flow": map[string]any{
			"format":  "mermaid",
			"mermaid": rpObjectLookupMermaid,
		},
		"external_key_flow": map[string]any{
			"format":  "mermaid",
			"mermaid": rpExternalKeyMermaid,
		},
		"audit_contract": map[string]any{
			"mutating_command_mode":        "fail_closed_sync",
			"missing_audit_sink_behavior":  "reject_mutation",
			"read_audit_mode":              "best_effort_allowlisted",
			"correlation_header":           "X-Request-Id",
			"rp_business_audit_required":   true,
			"baron_gateway_audit_required": true,
			"required_detail_fields": []string{
				"obj_id",
				"relation",
				"client_id",
				"subject",
				"decision",
			},
			"guarantee_scope": "Baron-mediated IAM mutations fail closed on audit write failure; RP-owned business events must be emitted by the RP with the same correlation header.",
		},
		"security_requirements": map[string]any{
			"strip_external_identity_headers": true,
			"backend_direct_exposure_allowed": false,
			"static_snapshot_requires_auth":   true,
			"email_as_primary_key_allowed":    false,
		},
	}
}

func rpManifestSchema() map[string]any {
	return map[string]any{
		"$schema": "https://json-schema.org/draft/2020-12/schema",
		"title":   "Baron RP IAM Manifest",
		"type":    "object",
		"required": []string{
			"version",
			"issuer",
			"oidc",
			"iam",
			"trusted_headers",
			"identity_contract",
			"object_lookup",
			"object_lookup_flow",
			"external_key_flow",
			"audit_contract",
			"security_requirements",
		},
		"properties": map[string]any{
			"version":               map[string]any{"type": "string"},
			"issuer":                map[string]any{"type": "string", "format": "uri"},
			"oidc":                  map[string]any{"type": "object"},
			"iam":                   map[string]any{"type": "object"},
			"trusted_headers":       map[string]any{"type": "object"},
			"identity_contract":     map[string]any{"type": "object"},
			"object_lookup":         map[string]any{"type": "object"},
			"object_lookup_flow":    map[string]any{"type": "object"},
			"external_key_flow":     map[string]any{"type": "object"},
			"audit_contract":        map[string]any{"type": "object"},
			"security_requirements": map[string]any{"type": "object"},
		},
	}
}
