package oci

import (
	"errors"
	"regexp"
	"strings"

	"github.com/oracle/oci-go-sdk/v65/common"
)

// ociErrorHints 把常见 OCI 错误码翻译成给用户看的中文提示。
var ociErrorHints = map[string]string{
	"IncorrectState":          "资源当前状态不允许该操作，通常是仍被其他资源占用或正在变更中",
	"NotAuthorizedOrNotFound": "无权限或资源不存在（可能已被删除）",
	"LimitExceeded":           "已达到服务配额上限",
	"QuotaExceeded":           "compartment 配额不足",
	"OutOfHostCapacity":       "该可用域容量不足，可稍后重试或改用抢机任务",
	"TooManyRequests":         "请求过于频繁，请稍后再试",
	"InvalidParameter":        "请求参数无效",
	"InternalError":           "OCI 服务内部错误，请稍后重试",
	"Conflict":                "资源正在变更中，请稍后重试",
}

// ocidRe 匹配消息中的完整 OCID（unique 段 20 位以上才压缩，避免误伤短标识）。
var ocidRe = regexp.MustCompile(`ocid1\.([a-z0-9]+)\.[a-z0-9]*\.[a-z0-9-]*\.{1,2}[a-z0-9]{20,}`)

// shortenOcids 把消息里的长 OCID 压缩为「ocid1.<类型>…<尾6位>」，
// 保留资源类型与可比对的尾部，避免整条错误被 OCID 撑爆。
func shortenOcids(s string) string {
	return ocidRe.ReplaceAllStringFunc(s, func(m string) string {
		i := strings.Index(m, ".")
		typ := m[i+1:]
		if j := strings.Index(typ, "."); j > 0 {
			typ = typ[:j]
		}
		return "ocid1." + typ + "…" + m[len(m)-6:]
	})
}

// CompactError 把含 OCI ServiceError 的错误链提炼成短消息：
// 操作前缀 + 服务端消息，剥掉 SDK 附加的错误码、排障链接、
// 时间戳、SDK 版本等长文（错误码另经 API 的 ociCode 字段透出）；
// 消息中的长 OCID 压缩为类型加尾位的短形式。非 OCI 错误原样返回。
func CompactError(err error) string {
	var svcErr common.ServiceError
	if !errors.As(err, &svcErr) {
		return shortenOcids(err.Error())
	}
	prefix := err.Error()
	if i := strings.Index(prefix, ": Error returned by"); i > 0 {
		prefix = prefix[:i] + ": "
	} else {
		prefix = ""
	}
	return shortenOcids(prefix + svcErr.GetMessage())
}

// ErrorHint 返回常见 OCI 错误的中文提示；未识别返回空串。
// 容量不足常以 code=InternalError 返回，按消息内容归到容量提示。
func ErrorHint(err error) string {
	var svcErr common.ServiceError
	if !errors.As(err, &svcErr) {
		return ""
	}
	if strings.Contains(svcErr.GetMessage(), "Out of host capacity") {
		return ociErrorHints["OutOfHostCapacity"]
	}
	return ociErrorHints[svcErr.GetCode()]
}

// ServiceStatus 返回错误链中 OCI ServiceError 的 HTTP 状态码;非服务端错误 ok=false。
func ServiceStatus(err error) (int, bool) {
	var svcErr common.ServiceError
	if errors.As(err, &svcErr) {
		return svcErr.GetHTTPStatusCode(), true
	}
	return 0, false
}

// IsOnDemandUnsupported 识别「模型在该区域仅为微调基座、不支持按需调用」的 400:
// OCI 消息形如 "Not allowed to call finetune base model …, use Endpoint: false"。
// ListModels 无字段可事先区分,只能在调用报错时识别并换渠道。
func IsOnDemandUnsupported(err error) bool {
	var svcErr common.ServiceError
	if !errors.As(err, &svcErr) {
		return false
	}
	return svcErr.GetHTTPStatusCode() == 400 &&
		strings.Contains(strings.ToLower(svcErr.GetMessage()), "finetune base model")
}

// IsEntityNotFound 识别「实体不存在」404(消息 "Entity with key … not found"):
// GenAI 对区域内无按需供给的模型 OCID 返回此类 404,属模型级错误;
// 鉴权失败的 404 是 NotAuthorizedOrNotFound(消息为 Authorization failed…),不在此列。
func IsEntityNotFound(err error) bool {
	var svcErr common.ServiceError
	if !errors.As(err, &svcErr) {
		return false
	}
	msg := strings.ToLower(svcErr.GetMessage())
	return svcErr.GetHTTPStatusCode() == 404 &&
		strings.Contains(msg, "entity with key") && strings.Contains(msg, "not found")
}

// IsModelUnavailable 归并「模型×区域不可按需调用」两类报错:微调基座 400 与实体不存在 404;
// 命中即应把该 (渠道, 模型) 从池中剔除并换候选/换渠道,而非定论租户配额问题。
func IsModelUnavailable(err error) bool {
	return IsOnDemandUnsupported(err) || IsEntityNotFound(err)
}
