// Package apierr defines the canonical error response shape used across all
// v1 API handlers: {code, message_zh, message_en}. It provides constructor
// helpers for common HTTP error categories (400/401/403/404/409/429/500)
// and a middleware that serialises *Error values to JSON automatically.
//
// Error messages follow the desensitisation rules: no "VPN" / "翻墙" /
// "科学上网" wording is permitted in any user-facing message.
package apierr

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

// Error is the canonical API error body: {code, message_zh, message_en}.
// All v1 handlers must return errors in this shape — never raw strings.
type Error struct {
	Code      string `json:"code"`
	MessageZH string `json:"message_zh"`
	MessageEn string `json:"message_en"`
}

// Error implements the error interface.
func (e *Error) Error() string { return e.Code + ": " + e.MessageEn }

// New creates a new *Error with an application error code and bilingual messages.
// Use this when none of the predefined errors fit the situation.
func New(code, messageZH, messageEn string) *Error {
	return &Error{Code: code, MessageZH: messageZH, MessageEn: messageEn}
}

// ─────────────────────────────────────────────────────────────────────────────
// Predefined errors — common HTTP error categories
// ─────────────────────────────────────────────────────────────────────────────

// General HTTP-category errors (400 / 401 / 403 / 404 / 409 / 429 / 500).
var (
	ErrBadRequest = &Error{
		Code:      "BAD_REQUEST",
		MessageZH: "请求参数有误",
		MessageEn: "Invalid request parameters",
	}
	ErrUnauthorized = &Error{
		Code:      "UNAUTHORIZED",
		MessageZH: "请先登录",
		MessageEn: "Authentication required",
	}
	ErrForbidden = &Error{
		Code:      "FORBIDDEN",
		MessageZH: "权限不足",
		MessageEn: "Permission denied",
	}
	ErrNotFound = &Error{
		Code:      "NOT_FOUND",
		MessageZH: "资源不存在",
		MessageEn: "Resource not found",
	}
	ErrConflict = &Error{
		Code:      "CONFLICT",
		MessageZH: "资源状态冲突",
		MessageEn: "Resource state conflict",
	}
	ErrRateLimited = &Error{
		Code:      "RATE_LIMITED",
		MessageZH: "操作过于频繁，请稍后再试",
		MessageEn: "Too many attempts, please try again later",
	}
	ErrInternal = &Error{
		Code:      "INTERNAL_ERROR",
		MessageZH: "服务器内部错误，请稍后重试",
		MessageEn: "Internal server error, please try again later",
	}
	ErrAccountBanned = &Error{
		Code:      "ACCOUNT_BANNED",
		MessageZH: "账户已被封禁，无法继续操作",
		MessageEn: "This account has been banned",
	}
)

// Activation-code errors.
var (
	ErrInvalidCode = &Error{
		Code:      "INVALID_CODE",
		MessageZH: "激活码格式无效，请检查后重试",
		MessageEn: "Invalid code format, please verify and try again",
	}
	ErrCodeNotFound = &Error{
		Code:      "CODE_NOT_FOUND",
		MessageZH: "激活码无效或已使用",
		MessageEn: "Code not found or already used",
	}
	ErrCodeRedeemed = &Error{
		Code:      "CODE_REDEEMED",
		MessageZH: "该激活码已被其他账户使用",
		MessageEn: "This code has already been redeemed by another account",
	}
	ErrCodeVoid = &Error{
		Code:      "CODE_VOID",
		MessageZH: "该激活码已失效",
		MessageEn: "This code is no longer valid",
	}
	ErrLocked = &Error{
		Code:      "ACCOUNT_LOCKED",
		MessageZH: "账户已临时锁定，请1小时后重试",
		MessageEn: "Account temporarily locked, please retry in 1 hour",
	}
)

// Usage / ad errors.
var (
	ErrAdNotUnlocked = &Error{
		Code:      "AD_NOT_UNLOCKED",
		MessageZH: "请先观看激励视频解锁当日时长",
		MessageEn: "Please watch the rewarded ad to unlock today's minutes",
	}
	ErrQuotaExhausted = &Error{
		Code:      "QUOTA_EXHAUSTED",
		MessageZH: "今日免费时长已用尽，请明天再来或升级套餐",
		MessageEn: "Today's free minutes are used up, try tomorrow or upgrade",
	}
	ErrAdVerifyFailed = &Error{
		Code:      "AD_VERIFY_FAILED",
		MessageZH: "广告回执校验失败",
		MessageEn: "Ad receipt verification failed",
	}
	ErrAdReplay = &Error{
		Code:      "AD_TOKEN_REPLAY",
		MessageZH: "该广告回执已被使用",
		MessageEn: "This ad receipt has already been used",
	}
)

// Webhook-specific errors.
var (
	ErrWebhookSignature = &Error{
		Code:      "WEBHOOK_INVALID_SIGNATURE",
		MessageZH: "签名校验失败",
		MessageEn: "Invalid webhook signature",
	}
	ErrWebhookTimestamp = &Error{
		Code:      "WEBHOOK_TIMESTAMP_EXPIRED",
		MessageZH: "请求时间戳超出允许窗口",
		MessageEn: "Webhook timestamp outside allowed window",
	}
	ErrWebhookReplay = &Error{
		Code:      "WEBHOOK_REPLAY",
		MessageZH: "重复请求已忽略",
		MessageEn: "Duplicate webhook request ignored",
	}
)

// ─────────────────────────────────────────────────────────────────────────────
// HTTP helpers
// ─────────────────────────────────────────────────────────────────────────────

// StatusFor returns a suitable HTTP status code for the given *Error, inferred
// from the error Code string. It covers the standard mapping used across all
// v1 handlers; callers may override with explicit WriteJSON calls when needed.
func StatusFor(e *Error) int {
	switch e.Code {
	case "UNAUTHORIZED":
		return http.StatusUnauthorized
	case "FORBIDDEN":
		return http.StatusForbidden
	case "NOT_FOUND":
		return http.StatusNotFound
	case "CONFLICT":
		return http.StatusConflict
	case "ACCOUNT_BANNED":
		return http.StatusForbidden
	case "RATE_LIMITED", "ACCOUNT_LOCKED":
		return http.StatusTooManyRequests
	case "INTERNAL_ERROR":
		return http.StatusInternalServerError
	default:
		return http.StatusBadRequest
	}
}

// WriteJSON writes the given HTTP status code and error body as JSON.
func WriteJSON(w http.ResponseWriter, status int, e *Error) {
	w.Header().Set("Content-Type", "application/json; charset=utf-8")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(e)
}

// ─────────────────────────────────────────────────────────────────────────────
// Chi-compatible middleware
// ─────────────────────────────────────────────────────────────────────────────

// Middleware is a chi-compatible middleware that recovers from panics of type
// *Error and writes the appropriate JSON response via StatusFor + WriteJSON.
// Any panic with a non-*Error value is re-raised so other recovery middleware
// (e.g. chi's built-in Recoverer) can handle it.
//
// Usage in handlers — instead of:
//
//	apierr.WriteJSON(w, http.StatusBadRequest, apierr.ErrBadRequest)
//	return
//
// A handler may simply:
//
//	panic(apierr.ErrBadRequest)
//
// This keeps handler code linear and avoids partial-write bugs when the caller
// forgets to return after WriteJSON.
func Middleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		defer func() {
			if rv := recover(); rv != nil {
				if e, ok := rv.(*Error); ok {
					WriteJSON(w, StatusFor(e), e)
					return
				}
				// Unknown panic type — re-raise for upstream recovery middleware.
				panic(rv)
			}
		}()
		next.ServeHTTP(w, r)
	})
}
