package agentapi

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

// R-82 Slice B — the per-tier backup surface (agent >= v0.97.0).
//
// Every method here is ADDITIVE. The untargeted BackupDue/StartBackup/BackupStatus keep their exact
// pre-R-82 meaning and are still the single-tier path used against an older agent.

// ErrTiersUnsupported reports that this agent does not serve GET /backup/tiers — it predates R-82.
// It is the DESIGNED capability probe (the route 404s), not a fault. The caller MUST degrade to the
// untargeted single-tier path and still take a backup; concluding "nothing to do" from it would
// silently stop backups during a fleet rollout.
var ErrTiersUnsupported = errors.New("agentapi: agent does not serve /backup/tiers (pre-R-82)")

// BackupTierInfo is one advertised tier.
type BackupTierInfo struct {
	Target         string `json:"target"`
	CadenceSeconds int64  `json:"cadence_seconds"`
	Primary        bool   `json:"primary"`
}

// TiersResponse mirrors the agent's GET /backup/tiers payload.
type TiersResponse struct {
	VMID  int              `json:"vmid"`
	Tiers []BackupTierInfo `json:"tiers"`
}

// BackupTiers lists the agent's backup tiers, primary first.
// Returns ErrTiersUnsupported (wrapped) on a pre-R-82 agent — key on it with errors.Is.
func (c *Client) BackupTiers(ctx context.Context) (TiersResponse, error) {
	var out TiersResponse
	body, err := c.get(ctx, "/backup/tiers")
	if err != nil {
		var se *StatusError
		if errors.As(err, &se) && se.Code == http.StatusNotFound {
			return out, ErrTiersUnsupported
		}
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode /backup/tiers: %w", err)
	}
	return out, nil
}

// targetQuery renders the ?target= suffix. An EMPTY target yields an empty string, so the caller
// hits the untargeted route byte-for-byte — that is what keeps the pre-R-82 contract intact when
// this client talks to an older agent.
func targetQuery(target string) string {
	if target == "" {
		return ""
	}
	return "?target=" + url.QueryEscape(target)
}

// BackupDueFor reports whether THIS TIER is due. A fresh backup on another tier must not satisfy it
// — that filtering happens agent-side (latestSuccessfulBackupForTarget); this just asks per tier.
func (c *Client) BackupDueFor(ctx context.Context, target string) (DueResponse, error) {
	var out DueResponse
	body, err := c.get(ctx, "/backup/due"+targetQuery(target))
	if err != nil {
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode /backup/due (target %q): %w", target, err)
	}
	return out, nil
}

// StartBackupFor enqueues a backup of this guest ON THE GIVEN TIER.
func (c *Client) StartBackupFor(ctx context.Context, target string) (BackupResponse, error) {
	var out BackupResponse
	body, err := c.post(ctx, "/backup"+targetQuery(target), struct{}{})
	if err != nil {
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode POST /backup (target %q): %w", target, err)
	}
	return out, nil
}

// BackupStatusFor reports THIS TIER's current/last job phase. Jobs are keyed per tier agent-side,
// so polling the wrong target would report a different tier's progress.
func (c *Client) BackupStatusFor(ctx context.Context, target string) (StatusResponse, error) {
	var out StatusResponse
	body, err := c.get(ctx, "/backup/status"+targetQuery(target))
	if err != nil {
		return out, err
	}
	if err := json.Unmarshal(body, &out); err != nil {
		return out, fmt.Errorf("agentapi: decode /backup/status (target %q): %w", target, err)
	}
	return out, nil
}
