/**
 * stateful.ts — 상태 API 순차 테스트 유틸리티
 *
 * closebook: stateful-api-testing
 * Hono app.request() 기반으로 여러 API 단계를 순차 실행.
 * 이전 응답에서 파라미터를 추출해 다음 요청에 주입.
 */

import { z } from "zod"

export interface ApiStep {
	name: string
	request: { method: string; path: string; body?: unknown }
	/** 이전 응답에서 파라미터 추출 → 다음 요청에 주입 */
	extract?: Record<string, (body: unknown) => unknown>
	validate: { status: number; body?: z.ZodType }
}

export interface StepResult {
	step: string
	status: number
	body: unknown
	extracted: Record<string, unknown>
	ok: boolean
	violations: Array<{ path: string; message: string }>
}

export interface SequenceResult {
	ok: boolean
	steps: StepResult[]
	captured: Record<string, unknown>
}

// 경로 파라미터 치환: "/session/{sessionId}" → "/session/abc123"
function resolvePath(template: string, vars: Record<string, unknown>): string {
	return template.replace(/\{(\w+)\}/g, (_, key) => String(vars[key] ?? `{${key}}`))
}

// 바디 내 {key} 치환
function resolveBody(body: unknown, vars: Record<string, unknown>): unknown {
	if (typeof body !== "object" || body === null) return body
	const str = JSON.stringify(body)
	const resolved = str.replace(/\{(\w+)\}/g, (_, key) => {
		const val = vars[key]
		return val !== undefined ? String(val) : `{${key}}`
	})
	return JSON.parse(resolved)
}

type TestableApp = {
	request(input: string | URL | Request, init?: RequestInit): Response | Promise<Response>
}

// 순차 API 테스트 실행
export async function runSequence(cfg: {
	app: TestableApp
	steps: ApiStep[]
	headers?: Record<string, string>
}): Promise<SequenceResult> {
	const { app, steps, headers: baseHeaders } = cfg
	const captured: Record<string, unknown> = {}
	const results: StepResult[] = []

	for (const step of steps) {
		const path = resolvePath(step.request.path, captured)
		const body = step.request.body ? resolveBody(step.request.body, captured) : undefined

		const init: RequestInit = { method: step.request.method, headers: baseHeaders }
		if (body !== undefined) {
			init.body = JSON.stringify(body)
			const merged = { ...(init.headers as Record<string, string>), "Content-Type": "application/json" }
			init.headers = merged
		}

		const res = await app.request(path, init)
		const resBody = await res.json()

		// 응답 검증
		const violations: StepResult["violations"] = []
		if (res.status !== step.validate.status) {
			violations.push({ path: "status", message: `Expected ${step.validate.status}, got ${res.status}` })
		}
		if (step.validate.body) {
			const parsed = step.validate.body.safeParse(resBody)
			if (!parsed.success) {
				for (const issue of parsed.error.issues) {
					violations.push({ path: issue.path.join(".") || "(root)", message: issue.message })
				}
			}
		}

		// 응답에서 파라미터 추출
		const extracted: Record<string, unknown> = {}
		if (step.extract) {
			for (const [key, fn] of Object.entries(step.extract)) {
				extracted[key] = fn(resBody)
				captured[key] = extracted[key]
			}
		}

		results.push({
			step: step.name,
			status: res.status,
			body: resBody,
			extracted,
			ok: violations.length === 0,
			violations,
		})
	}

	return { ok: results.every((r) => r.ok), steps: results, captured }
}
