package cmd

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"forgejo.zerova.net/public/fj/internal/api"
)

// Error codes for structured error output.
const (
	ErrAuthRequired       = "auth_required"
	ErrNotFound           = "not_found"
	ErrAPIError           = "api_error"
	ErrInvalidInput       = "invalid_input"
	ErrGitDetectionFailed = "git_detection_failed"
	ErrNetworkError       = "network_error"
)

// CLIError is a structured error type for machine-readable output.
type CLIError struct {
	Code    string `json:"code"`
	Message string `json:"message"`
	Detail  string `json:"detail,omitempty"`
	Status  int    `json:"status,omitempty"`
}

func (e *CLIError) Error() string {
	return e.Message
}

// NewCLIError creates a new CLIError with the given code and message.
func NewCLIError(code, message string) *CLIError {
	return &CLIError{Code: code, Message: message}
}

// NewAPIError creates a CLIError from an HTTP status and message.
func NewAPIError(status int, message string) *CLIError {
	return &CLIError{Code: ErrAPIError, Message: message, Status: status}
}

// ContextualError wraps common errors with helpful hints.
func ContextualError(err error) error {
	if err == nil {
		return nil
	}

	msg := err.Error()

	// Check for API errors with status codes
	var apiErr *api.APIError
	if errors.As(err, &apiErr) {
		switch {
		case apiErr.StatusCode == 401 || apiErr.StatusCode == 403:
			return fmt.Errorf("%w\nHint: Try authenticating with: fj auth login", err)
		case apiErr.StatusCode == 404:
			return fmt.Errorf("%w\nHint: Resource not found. Check the repository and number are correct.", err)
		}
		return err
	}

	// Check for network/connection errors
	switch {
	case strings.Contains(msg, "no such host"):
		return fmt.Errorf("%w\nHint: Check your internet connection and that the host is correct.", err)
	case strings.Contains(msg, "connection refused"):
		return fmt.Errorf("%w\nHint: Check your internet connection and that the host is correct.", err)
	}

	// Check for string-based status code patterns (from wrapped errors)
	switch {
	case strings.Contains(msg, "401") || strings.Contains(msg, "403"):
		if strings.Contains(msg, "authentication") || strings.Contains(msg, "unauthorized") || strings.Contains(msg, "forbidden") {
			return fmt.Errorf("%w\nHint: Try authenticating with: fj auth login", err)
		}
	}

	return err
}

// writeJSONError writes a structured JSON error to stderr.
// It attempts to extract structured info from known error types.
// WriteJSONError writes a structured JSON error to stderr.
// It is exported for use from main.go.
func WriteJSONError(err error) {
	cliErr := &CLIError{
		Code:    ErrAPIError,
		Message: err.Error(),
	}

	// Try to extract structured info from the error chain.
	var apiErr *api.APIError
	var cErr *CLIError

	switch {
	case errors.As(err, &cErr):
		cliErr = cErr
	case errors.As(err, &apiErr):
		cliErr.Status = apiErr.StatusCode
		cliErr.Detail = apiErr.Body
		switch {
		case apiErr.StatusCode == 401 || apiErr.StatusCode == 403:
			cliErr.Code = ErrAuthRequired
		case apiErr.StatusCode == 404:
			cliErr.Code = ErrNotFound
		default:
			cliErr.Code = ErrAPIError
		}
	}

	enc := json.NewEncoder(ios.ErrOut)
	enc.SetIndent("", "  ")
	_ = enc.Encode(cliErr)
}
