package handler

import (
	"encoding/json"
	"io"
	"net/http/httptest"
	"testing"

	"github.com/gofiber/fiber/v2"
	"github.com/stretchr/testify/require"
)

func TestRPManifestJSONIncludesIAMAndExternalKeyContract(t *testing.T) {
	t.Setenv("BACKEND_PUBLIC_URL", "")

	app := fiber.New()
	h := NewRPManifestHandler()
	app.Get("/.well-known/baron-rp-manifest.json", h.GetJSON)

	req := httptest.NewRequest("GET", "/.well-known/baron-rp-manifest.json", nil)
	req.Header.Set("X-Forwarded-Proto", "https")
	req.Header.Set("X-Forwarded-Host", "sso.hmac.kr")
	resp, err := app.Test(req)
	require.NoError(t, err)
	defer resp.Body.Close()

	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Contains(t, resp.Header.Get("Content-Type"), "application/json")

	var body map[string]any
	require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))
	require.Equal(t, "https://sso.hmac.kr", body["issuer"])

	oidc := body["oidc"].(map[string]any)
	require.Equal(t, "https://sso.hmac.kr/.well-known/openid-configuration", oidc["discovery_url"])
	require.Equal(t, "https://sso.hmac.kr/.well-known/jwks.json", oidc["jwks_url"])

	iam := body["iam"].(map[string]any)
	require.Equal(t, "ory-keto", iam["authorization_engine"])
	require.Equal(t, "User:<baron_identity_id>", iam["subject_format"])
	require.Contains(t, iam["target_object_patterns"].([]any), "RelyingParty:<client_id>")
	require.Contains(t, iam["target_object_patterns"].([]any), "Tenant:<tenant_id>")
	require.Contains(t, iam["target_object_patterns"].([]any), "Resource:<resource_type>:<resource_id>")

	identity := body["identity_contract"].(map[string]any)
	require.Equal(t, "X-Baron-External-Key", identity["external_key_header"])
	require.Equal(t, true, identity["external_key_is_opaque"])
	require.Equal(t, false, identity["raw_kratos_identity_id_exposed"])
	require.Equal(t, "baron", identity["external_key_issuer"])
	require.Equal(t, "baron_injected_header", identity["external_key_delivery"])
	require.Equal(t, false, identity["rp_supplied_external_key_allowed"])
	require.Equal(t, "rp_must_upsert_from_header_value", identity["rp_user_upsert_source"])

	headers := body["trusted_headers"].(map[string]any)
	require.Equal(t, "X-Baron-Subject", headers["subject"])
	require.Equal(t, "X-Baron-External-Key", headers["external_key"])
	require.Equal(t, "X-Baron-Client-ID", headers["client_id"])

	security := body["security_requirements"].(map[string]any)
	require.Equal(t, true, security["strip_external_identity_headers"])
	require.Equal(t, false, security["backend_direct_exposure_allowed"])

	audit := body["audit_contract"].(map[string]any)
	require.Equal(t, "fail_closed_sync", audit["mutating_command_mode"])
	require.Equal(t, "reject_mutation", audit["missing_audit_sink_behavior"])
	require.Equal(t, "X-Request-Id", audit["correlation_header"])
	require.Contains(t, audit["required_detail_fields"].([]any), "obj_id")
	require.Contains(t, audit["required_detail_fields"].([]any), "client_id")

	flow := body["object_lookup_flow"].(map[string]any)
	require.Contains(t, flow["mermaid"].(string), "flowchart TD")
	require.Contains(t, flow["mermaid"].(string), "obj_id")

	aliasFlow := body["external_key_flow"].(map[string]any)
	require.Contains(t, aliasFlow["mermaid"].(string), "Baron resolves internal identity")
	require.Contains(t, aliasFlow["mermaid"].(string), "Baron injects X-Baron-External-Key")
	require.Contains(t, aliasFlow["mermaid"].(string), "RP upserts local user")
	require.NotContains(t, aliasFlow["mermaid"].(string), "RP creates external key")
}

func TestRPManifestSchemaRequiresLookupAndIdentityContracts(t *testing.T) {
	app := fiber.New()
	h := NewRPManifestHandler()
	app.Get("/.well-known/baron-rp-manifest.schema.json", h.GetSchema)

	resp, err := app.Test(httptest.NewRequest("GET", "/.well-known/baron-rp-manifest.schema.json", nil))
	require.NoError(t, err)
	defer resp.Body.Close()

	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	var body map[string]any
	require.NoError(t, json.NewDecoder(resp.Body).Decode(&body))

	required := body["required"].([]any)
	require.Contains(t, required, "iam")
	require.Contains(t, required, "trusted_headers")
	require.Contains(t, required, "identity_contract")
	require.Contains(t, required, "object_lookup")
	require.Contains(t, required, "audit_contract")
	require.Contains(t, required, "object_lookup_flow")
	require.Contains(t, required, "external_key_flow")
}

func TestRPManifestHTMLLinksMachineReadableManifest(t *testing.T) {
	app := fiber.New()
	h := NewRPManifestHandler()
	app.Get("/.well-known/baron-rp-manifest", h.GetHTML)

	resp, err := app.Test(httptest.NewRequest("GET", "/.well-known/baron-rp-manifest", nil))
	require.NoError(t, err)
	defer resp.Body.Close()

	require.Equal(t, fiber.StatusOK, resp.StatusCode)
	require.Contains(t, resp.Header.Get("Content-Type"), "text/html")
	raw, err := io.ReadAll(resp.Body)
	require.NoError(t, err)
	text := string(raw)
	require.Contains(t, text, "/.well-known/baron-rp-manifest.json")
	require.Contains(t, text, "X-Baron-External-Key")
	require.Contains(t, text, "RelyingParty:&lt;client_id&gt;")
	require.Contains(t, text, "```mermaid")
	require.Contains(t, text, "audit_contract")
	require.Contains(t, text, "Baron-issued alias")
	require.Contains(t, text, "RP upserts local user")
}
