package agentapi

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
)

// Controller-driven escrow ceremony client methods (v0.127.0, agent ≥ v0.88.0). The claim call
// is the ONLY place the recovery code R crosses this client — its response body must never be
// logged (the shared helpers log path/status/duration only, never bodies) and the caller hands
// R straight to the wizard's claim XHR, nowhere else.

// EscrowPreflightItem mirrors one agent preflight checklist row.
type EscrowPreflightItem struct {
	ID     string `json:"id"`
	OK     bool   `json:"ok"`
	Detail string `json:"detail"`
}

// EscrowPreflightResponse mirrors GET /escrow/preflight.
type EscrowPreflightResponse struct {
	VMID  int                   `json:"vmid"`
	OK    bool                  `json:"ok"`
	Items []EscrowPreflightItem `json:"items"`
}

// EscrowPreflight fetches the agent's ceremony prerequisite checklist.
func (c *Client) EscrowPreflight(ctx context.Context) (EscrowPreflightResponse, error) {
	var out EscrowPreflightResponse
	body, err := c.get(ctx, "/escrow/preflight")
	if err != nil {
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode /escrow/preflight: %w", err)
	}
	return out, nil
}

// EscrowCeremonyStartResponse mirrors the POST /escrow/ceremony 202 payload.
type EscrowCeremonyStartResponse struct {
	JobID string `json:"job_id"`
	Phase string `json:"phase"`
}

// EscrowCeremonyStart triggers the agent's detached root ceremony job. Status-aware: the HTTP
// status is returned so the caller can map the agent's 409 (a ceremony already running) to its
// own house-style refusal.
func (c *Client) EscrowCeremonyStart(ctx context.Context) (EscrowCeremonyStartResponse, int, error) {
	var out EscrowCeremonyStartResponse
	env, status, err := c.postWithStatus(ctx, "/escrow/ceremony", struct{}{})
	if err != nil {
		return out, status, err
	}
	if err := refusalError("/escrow/ceremony", status, env); err != nil {
		return out, status, err
	}
	if err := json.Unmarshal(env.Data, &out); err != nil {
		return out, status, fmt.Errorf("agentapi: decode /escrow/ceremony: %w", err)
	}
	return out, status, nil
}

// EscrowCeremonyStatusResponse mirrors GET /escrow/ceremony/status — the NON-SECRET job view
// (R is structurally absent from the agent's payload).
type EscrowCeremonyStatusResponse struct {
	Phase             string  `json:"phase"` // none | running | done | failed | unclaimed_void
	JobID             string  `json:"job_id"`
	KeyFingerprint    string  `json:"key_fingerprint"`
	EntropyBits       float64 `json:"entropy_bits"`
	ResticPwSealed    bool    `json:"restic_pw_sealed"`
	Uploaded          bool    `json:"uploaded"`
	Claimable         bool    `json:"claimable"`
	Claimed           bool    `json:"claimed"`
	ClaimExpiresInSec int     `json:"claim_expires_in_sec"`
	Detail            string  `json:"detail"`
}

// EscrowCeremonyStatus polls the ceremony job.
func (c *Client) EscrowCeremonyStatus(ctx context.Context) (EscrowCeremonyStatusResponse, error) {
	var out EscrowCeremonyStatusResponse
	body, err := c.get(ctx, "/escrow/ceremony/status")
	if err != nil {
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode /escrow/ceremony/status: %w", err)
	}
	return out, nil
}

// EscrowCeremonyClaim performs the ONE-SHOT R claim. Returns the recovery code + the agent's
// HTTP status (410 = already claimed / expired — the wizard's void state). The code must never
// be logged, persisted, or placed anywhere but the claim XHR response; the error path carries
// the agent's reason text, never the code.
func (c *Client) EscrowCeremonyClaim(ctx context.Context) (string, int, error) {
	env, status, err := c.postWithStatus(ctx, "/escrow/ceremony/claim", struct{}{})
	if err != nil {
		return "", status, err
	}
	if status == http.StatusGone {
		return "", status, fmt.Errorf("agentapi: POST /escrow/ceremony/claim: gone (claimed or expired)")
	}
	if err := refusalError("/escrow/ceremony/claim", status, env); err != nil {
		return "", status, err
	}
	var out struct {
		RecoveryCode string `json:"recovery_code"`
	}
	if err := json.Unmarshal(env.Data, &out); err != nil {
		return "", status, fmt.Errorf("agentapi: decode /escrow/ceremony/claim: %w", err)
	}
	if out.RecoveryCode == "" {
		return "", status, fmt.Errorf("agentapi: /escrow/ceremony/claim returned no code")
	}
	return out.RecoveryCode, status, nil
}
