package proxmox

import (
	"fmt"
	"regexp"
)

// permRe extracts the offending privilege (and path) from a Proxmox permission
// message, e.g. "Permission check failed (/vms/9000, VM.Backup)" or
// "403 Permission check failed (/sdn/zones/localnetwork/vmbr0, SDN.Use)".
var permRe = regexp.MustCompile(`Permission check failed \(([^,]+),\s*([^)]+)\)`)

// APIError is returned for a non-2xx HTTP response from the Proxmox API. On a 403
// it parses the offending path + privilege so a role misconfiguration is
// diagnosable (the FelhomAgent role is exactly 16 privileges — see doc.go).
type APIError struct {
	StatusCode int
	Method     string
	Path       string // request path
	Body       string // response body (trimmed)
	// Populated from a permission-check message when present:
	DeniedPath string // ACL path, e.g. "/vms/9000"
	Privilege  string // e.g. "VM.Backup"
}

func (e *APIError) Error() string {
	if e.Privilege != "" {
		return fmt.Sprintf("proxmox: %s %s -> HTTP %d: permission denied at %s (missing privilege %s)",
			e.Method, e.Path, e.StatusCode, e.DeniedPath, e.Privilege)
	}
	return fmt.Sprintf("proxmox: %s %s -> HTTP %d: %s", e.Method, e.Path, e.StatusCode, e.Body)
}

// IsForbidden reports whether this was an HTTP 403.
func (e *APIError) IsForbidden() bool { return e.StatusCode == 403 }

// newAPIError builds an APIError, extracting privilege info from the body.
func newAPIError(statusCode int, method, path, body string) *APIError {
	e := &APIError{StatusCode: statusCode, Method: method, Path: path, Body: trimBody(body)}
	if m := permRe.FindStringSubmatch(body); m != nil {
		e.DeniedPath = m[1]
		e.Privilege = m[2]
	}
	return e
}

// TaskError is returned by WaitTask when a task stops with a non-OK exitstatus.
// The authorization failure for a mutating op surfaces here (in the task
// exitstatus), not at the HTTP POST — so callers must always WaitTask.
type TaskError struct {
	UPID       string
	ExitStatus string   // e.g. "403 Permission check failed (/vms/9000, VM.Backup)"
	LogTail    []string // last lines of the task log, for diagnosis
	DeniedPath string
	Privilege  string
}

func (e *TaskError) Error() string {
	if e.Privilege != "" {
		return fmt.Sprintf("proxmox: task %s failed: permission denied at %s (missing privilege %s)",
			e.UPID, e.DeniedPath, e.Privilege)
	}
	return fmt.Sprintf("proxmox: task %s failed: exitstatus %q", e.UPID, e.ExitStatus)
}

func newTaskError(upid, exitStatus string, logTail []string) *TaskError {
	e := &TaskError{UPID: upid, ExitStatus: exitStatus, LogTail: logTail}
	if m := permRe.FindStringSubmatch(exitStatus); m != nil {
		e.DeniedPath = m[1]
		e.Privilege = m[2]
	}
	return e
}

func trimBody(s string) string {
	const max = 512
	if len(s) > max {
		return s[:max] + "…"
	}
	return s
}
