// Copyright 2026 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package errors

import "net/http"

// ErrorCode represents a machine-readable error code
type ErrorCode string

// Authentication errors (AUTH_)
const (
	AuthTokenMissing       ErrorCode = "AUTH_TOKEN_MISSING"
	AuthTokenInvalid       ErrorCode = "AUTH_TOKEN_INVALID"
	AuthTokenExpired       ErrorCode = "AUTH_TOKEN_EXPIRED"
	AuthScopeInsufficient  ErrorCode = "AUTH_SCOPE_INSUFFICIENT"
	Auth2FARequired        ErrorCode = "AUTH_2FA_REQUIRED"
	AuthInvalidCredentials ErrorCode = "AUTH_INVALID_CREDENTIALS"
)

// Permission errors (PERM_)
const (
	PermRepoReadDenied    ErrorCode = "PERM_REPO_READ_DENIED"
	PermRepoWriteDenied   ErrorCode = "PERM_REPO_WRITE_DENIED"
	PermRepoAdminRequired ErrorCode = "PERM_REPO_ADMIN_REQUIRED"
	PermOrgMemberRequired ErrorCode = "PERM_ORG_MEMBER_REQUIRED"
	PermOrgAdminRequired  ErrorCode = "PERM_ORG_ADMIN_REQUIRED"
	PermActionDenied      ErrorCode = "PERM_ACTION_DENIED"
)

// Repository errors (REPO_)
const (
	RepoNotFound        ErrorCode = "REPO_NOT_FOUND"
	RepoArchived        ErrorCode = "REPO_ARCHIVED"
	RepoDisabled        ErrorCode = "REPO_DISABLED"
	RepoTransferPending ErrorCode = "REPO_TRANSFER_PENDING"
	RepoEmpty           ErrorCode = "REPO_EMPTY"
	RepoAlreadyExists   ErrorCode = "REPO_ALREADY_EXISTS"
)

// File errors (FILE_)
const (
	FileNotFound  ErrorCode = "FILE_NOT_FOUND"
	FileTooLarge  ErrorCode = "FILE_TOO_LARGE"
	FileConflict  ErrorCode = "FILE_CONFLICT"
	FileBinary    ErrorCode = "FILE_BINARY"
	FileTypeError ErrorCode = "FILE_TYPE_NOT_ALLOWED"
)

// Git errors (GIT_)
const (
	GitRefNotFound    ErrorCode = "GIT_REF_NOT_FOUND"
	GitMergeConflict  ErrorCode = "GIT_MERGE_CONFLICT"
	GitBranchNotFound ErrorCode = "GIT_BRANCH_NOT_FOUND"
	GitTagNotFound    ErrorCode = "GIT_TAG_NOT_FOUND"
	GitCommitNotFound ErrorCode = "GIT_COMMIT_NOT_FOUND"
	GitPushRejected   ErrorCode = "GIT_PUSH_REJECTED"
)

// Rate limiting errors (RATE_)
const (
	RateLimitExceeded ErrorCode = "RATE_LIMIT_EXCEEDED"
	RateQuotaExhausted ErrorCode = "RATE_QUOTA_EXHAUSTED"
)

// Validation errors (VAL_)
const (
	ValInvalidInput   ErrorCode = "VAL_INVALID_INPUT"
	ValMissingField   ErrorCode = "VAL_MISSING_FIELD"
	ValInvalidName    ErrorCode = "VAL_INVALID_NAME"
	ValNameTooLong    ErrorCode = "VAL_NAME_TOO_LONG"
	ValInvalidEmail   ErrorCode = "VAL_INVALID_EMAIL"
	ValDuplicateName  ErrorCode = "VAL_DUPLICATE_NAME"
	ValInvalidFormat  ErrorCode = "VAL_INVALID_FORMAT"
	ValidationFailed  ErrorCode = "VALIDATION_FAILED"
)

// General errors
const (
	InternalError    ErrorCode = "INTERNAL_ERROR"
	PermAccessDenied ErrorCode = "ACCESS_DENIED"
	RefNotFound      ErrorCode = "REF_NOT_FOUND"
)

// Upload errors (UPLOAD_)
const (
	UploadSessionNotFound    ErrorCode = "UPLOAD_SESSION_NOT_FOUND"
	UploadSessionExpired     ErrorCode = "UPLOAD_SESSION_EXPIRED"
	UploadChunkInvalid       ErrorCode = "UPLOAD_CHUNK_INVALID"
	UploadChunkSizeMismatch  ErrorCode = "UPLOAD_CHUNK_SIZE_MISMATCH"
	UploadChecksumMismatch   ErrorCode = "UPLOAD_CHECKSUM_MISMATCH"
	UploadIncomplete         ErrorCode = "UPLOAD_INCOMPLETE"
	UploadFileTooLarge       ErrorCode = "UPLOAD_FILE_TOO_LARGE"
)

// Resource errors (RESOURCE_)
const (
	ResourceNotFound    ErrorCode = "RESOURCE_NOT_FOUND"
	ResourceConflict    ErrorCode = "RESOURCE_CONFLICT"
	ResourceGone        ErrorCode = "RESOURCE_GONE"
)

// Server errors (SERVER_)
const (
	ServerInternal    ErrorCode = "SERVER_INTERNAL_ERROR"
	ServerUnavailable ErrorCode = "SERVER_UNAVAILABLE"
	ServerTimeout     ErrorCode = "SERVER_TIMEOUT"
)

// User errors (USER_)
const (
	UserNotFound      ErrorCode = "USER_NOT_FOUND"
	UserAlreadyExists ErrorCode = "USER_ALREADY_EXISTS"
	UserInactive      ErrorCode = "USER_INACTIVE"
	UserProhibitLogin ErrorCode = "USER_PROHIBIT_LOGIN"
)

// Organization errors (ORG_)
const (
	OrgNotFound      ErrorCode = "ORG_NOT_FOUND"
	OrgAlreadyExists ErrorCode = "ORG_ALREADY_EXISTS"
)

// Issue errors (ISSUE_)
const (
	IssueNotFound   ErrorCode = "ISSUE_NOT_FOUND"
	IssueClosed     ErrorCode = "ISSUE_CLOSED"
	IssueLocked     ErrorCode = "ISSUE_LOCKED"
)

// Pull Request errors (PR_)
const (
	PRNotFound        ErrorCode = "PR_NOT_FOUND"
	PRAlreadyMerged   ErrorCode = "PR_ALREADY_MERGED"
	PRNotMergeable    ErrorCode = "PR_NOT_MERGEABLE"
	PRWorkInProgress  ErrorCode = "PR_WORK_IN_PROGRESS"
)

// Release errors (RELEASE_)
const (
	ReleaseNotFound    ErrorCode = "RELEASE_NOT_FOUND"
	ReleaseTagExists   ErrorCode = "RELEASE_TAG_EXISTS"
	ReleaseIsDraft     ErrorCode = "RELEASE_IS_DRAFT"
)

// Webhook errors (WEBHOOK_)
const (
	WebhookNotFound     ErrorCode = "WEBHOOK_NOT_FOUND"
	WebhookDeliveryFail ErrorCode = "WEBHOOK_DELIVERY_FAILED"
)

// errorInfo contains metadata about an error code
type errorInfo struct {
	Message    string
	HTTPStatus int
}

// errorCatalog maps error codes to their metadata
var errorCatalog = map[ErrorCode]errorInfo{
	// Auth errors
	AuthTokenMissing:       {"No authentication token provided", http.StatusUnauthorized},
	AuthTokenInvalid:       {"Token is malformed or invalid", http.StatusUnauthorized},
	AuthTokenExpired:       {"Token has expired", http.StatusUnauthorized},
	AuthScopeInsufficient:  {"Token lacks required scope", http.StatusForbidden},
	Auth2FARequired:        {"Two-factor authentication required", http.StatusUnauthorized},
	AuthInvalidCredentials: {"Invalid username or password", http.StatusUnauthorized},

	// Permission errors
	PermRepoReadDenied:    {"Cannot read repository", http.StatusForbidden},
	PermRepoWriteDenied:   {"Cannot write to repository", http.StatusForbidden},
	PermRepoAdminRequired: {"Repository admin access required", http.StatusForbidden},
	PermOrgMemberRequired: {"Must be organization member", http.StatusForbidden},
	PermOrgAdminRequired:  {"Organization admin access required", http.StatusForbidden},
	PermActionDenied:      {"Permission denied for this action", http.StatusForbidden},

	// Repository errors
	RepoNotFound:        {"Repository does not exist", http.StatusNotFound},
	RepoArchived:        {"Repository is archived", http.StatusForbidden},
	RepoDisabled:        {"Repository is disabled", http.StatusForbidden},
	RepoTransferPending: {"Repository has pending transfer", http.StatusConflict},
	RepoEmpty:           {"Repository is empty", http.StatusUnprocessableEntity},
	RepoAlreadyExists:   {"Repository already exists", http.StatusConflict},

	// File errors
	FileNotFound:  {"File does not exist", http.StatusNotFound},
	FileTooLarge:  {"File exceeds size limit", http.StatusRequestEntityTooLarge},
	FileConflict:  {"File was modified (SHA mismatch)", http.StatusConflict},
	FileBinary:    {"Cannot perform text operation on binary file", http.StatusBadRequest},
	FileTypeError: {"File type not allowed", http.StatusBadRequest},

	// Git errors
	GitRefNotFound:    {"Git reference not found", http.StatusNotFound},
	GitMergeConflict:  {"Merge conflict detected", http.StatusConflict},
	GitBranchNotFound: {"Branch not found", http.StatusNotFound},
	GitTagNotFound:    {"Tag not found", http.StatusNotFound},
	GitCommitNotFound: {"Commit not found", http.StatusNotFound},
	GitPushRejected:   {"Push rejected", http.StatusForbidden},

	// Rate limiting errors
	RateLimitExceeded:  {"API rate limit exceeded", http.StatusTooManyRequests},
	RateQuotaExhausted: {"Rate quota exhausted", http.StatusTooManyRequests},

	// Validation errors
	ValInvalidInput:  {"Invalid input provided", http.StatusBadRequest},
	ValMissingField:  {"Required field is missing", http.StatusBadRequest},
	ValInvalidName:   {"Name contains invalid characters", http.StatusBadRequest},
	ValNameTooLong:   {"Name exceeds maximum length", http.StatusBadRequest},
	ValInvalidEmail:  {"Invalid email address", http.StatusBadRequest},
	ValDuplicateName: {"Name already exists", http.StatusConflict},
	ValInvalidFormat: {"Invalid format", http.StatusBadRequest},
	ValidationFailed: {"Validation failed", http.StatusBadRequest},

	// General errors
	InternalError:    {"Internal server error", http.StatusInternalServerError},
	PermAccessDenied: {"Access denied", http.StatusForbidden},
	RefNotFound:      {"Reference not found", http.StatusNotFound},

	// Upload errors
	UploadSessionNotFound:   {"Upload session does not exist", http.StatusNotFound},
	UploadSessionExpired:    {"Upload session has expired", http.StatusGone},
	UploadChunkInvalid:      {"Chunk number out of range", http.StatusBadRequest},
	UploadChunkSizeMismatch: {"Chunk size doesn't match expected", http.StatusBadRequest},
	UploadChecksumMismatch:  {"File checksum verification failed", http.StatusBadRequest},
	UploadIncomplete:        {"Not all chunks have been uploaded", http.StatusBadRequest},
	UploadFileTooLarge:      {"File exceeds maximum upload size", http.StatusRequestEntityTooLarge},

	// Resource errors
	ResourceNotFound: {"Resource not found", http.StatusNotFound},
	ResourceConflict: {"Resource conflict", http.StatusConflict},
	ResourceGone:     {"Resource no longer available", http.StatusGone},

	// Server errors
	ServerInternal:    {"Internal server error", http.StatusInternalServerError},
	ServerUnavailable: {"Service temporarily unavailable", http.StatusServiceUnavailable},
	ServerTimeout:     {"Request timeout", http.StatusGatewayTimeout},

	// User errors
	UserNotFound:      {"User not found", http.StatusNotFound},
	UserAlreadyExists: {"User already exists", http.StatusConflict},
	UserInactive:      {"User account is inactive", http.StatusForbidden},
	UserProhibitLogin: {"User is not allowed to login", http.StatusForbidden},

	// Organization errors
	OrgNotFound:      {"Organization not found", http.StatusNotFound},
	OrgAlreadyExists: {"Organization already exists", http.StatusConflict},

	// Issue errors
	IssueNotFound: {"Issue not found", http.StatusNotFound},
	IssueClosed:   {"Issue is closed", http.StatusUnprocessableEntity},
	IssueLocked:   {"Issue is locked", http.StatusForbidden},

	// Pull Request errors
	PRNotFound:       {"Pull request not found", http.StatusNotFound},
	PRAlreadyMerged:  {"Pull request already merged", http.StatusConflict},
	PRNotMergeable:   {"Pull request is not mergeable", http.StatusConflict},
	PRWorkInProgress: {"Pull request is marked as work in progress", http.StatusUnprocessableEntity},

	// Release errors
	ReleaseNotFound:  {"Release not found", http.StatusNotFound},
	ReleaseTagExists: {"Release tag already exists", http.StatusConflict},
	ReleaseIsDraft:   {"Release is a draft", http.StatusUnprocessableEntity},

	// Webhook errors
	WebhookNotFound:     {"Webhook not found", http.StatusNotFound},
	WebhookDeliveryFail: {"Webhook delivery failed", http.StatusBadGateway},
}

// Message returns the human-readable message for an error code
func (e ErrorCode) Message() string {
	if info, ok := errorCatalog[e]; ok {
		return info.Message
	}
	return string(e)
}

// HTTPStatus returns the HTTP status code for an error code
func (e ErrorCode) HTTPStatus() int {
	if info, ok := errorCatalog[e]; ok {
		return info.HTTPStatus
	}
	return http.StatusInternalServerError
}

// String returns the error code as a string
func (e ErrorCode) String() string {
	return string(e)
}

// Error implements the error interface
func (e ErrorCode) Error() string {
	return e.Message()
}

// IsValid returns true if the error code is registered in the catalog
func (e ErrorCode) IsValid() bool {
	_, ok := errorCatalog[e]
	return ok
}
