package backendclient

import (
	"context"
	"errors"
	"fmt"
	"net/http"
	"strings"
)

// SendEmailCodeInput is the public REST and adapter payload used to
// request a login code for a single e-mail address.
type SendEmailCodeInput struct {
	Email             string `json:"email"`
	PreferredLanguage string `json:"-"`
}

// SendEmailCodeResult is the public REST and adapter payload returned
// after backend creates a login challenge.
type SendEmailCodeResult struct {
	ChallengeID string `json:"challenge_id"`
}

// ConfirmEmailCodeInput is the public REST and adapter payload used to
// complete a previously issued login challenge.
type ConfirmEmailCodeInput struct {
	ChallengeID     string `json:"challenge_id"`
	Code            string `json:"code"`
	ClientPublicKey string `json:"client_public_key"`
	TimeZone        string `json:"time_zone"`
}

// ConfirmEmailCodeResult is the public REST and adapter payload
// returned after backend creates a device session.
type ConfirmEmailCodeResult struct {
	DeviceSessionID string `json:"device_session_id"`
}

// AuthError lets a public REST handler project a stable error envelope
// without re-deriving backend semantics. StatusCode is the HTTP status
// the gateway should return; Code and Message form the JSON envelope.
type AuthError struct {
	StatusCode int
	Code       string
	Message    string
}

// Error returns a readable representation of the projected auth error.
func (e *AuthError) Error() string {
	if e == nil {
		return ""
	}
	return fmt.Sprintf("backendclient auth error: status=%d code=%s message=%s", e.StatusCode, e.Code, e.Message)
}

// SendEmailCode delegates the public send-email-code route to backend.
func (c *RESTClient) SendEmailCode(ctx context.Context, input SendEmailCodeInput) (SendEmailCodeResult, error) {
	if strings.TrimSpace(input.Email) == "" {
		return SendEmailCodeResult{}, errors.New("backendclient: send email code: email must not be empty")
	}
	body, status, err := c.doWithHeaders(ctx, http.MethodPost, c.baseURL+"/api/v1/public/auth/send-email-code", "", input, map[string]string{
		"Accept-Language": resolvePreferredLanguage(input.PreferredLanguage),
	})
	if err != nil {
		return SendEmailCodeResult{}, fmt.Errorf("backendclient: send email code: %w", err)
	}
	switch {
	case status == http.StatusOK:
		var result SendEmailCodeResult
		if err := decodeStrictJSON(body, &result); err != nil {
			return SendEmailCodeResult{}, fmt.Errorf("backendclient: send email code: decode success response: %w", err)
		}
		if strings.TrimSpace(result.ChallengeID) == "" {
			return SendEmailCodeResult{}, errors.New("backendclient: send email code: challenge_id must not be empty")
		}
		return result, nil
	case status >= 400 && status <= 599:
		authErr, decodeErr := decodeAuthError(status, body)
		if decodeErr != nil {
			return SendEmailCodeResult{}, fmt.Errorf("backendclient: send email code: %w", decodeErr)
		}
		return SendEmailCodeResult{}, authErr
	default:
		return SendEmailCodeResult{}, fmt.Errorf("backendclient: send email code: unexpected HTTP status %d", status)
	}
}

// ConfirmEmailCode delegates the public confirm-email-code route to
// backend.
func (c *RESTClient) ConfirmEmailCode(ctx context.Context, input ConfirmEmailCodeInput) (ConfirmEmailCodeResult, error) {
	if strings.TrimSpace(input.ChallengeID) == "" {
		return ConfirmEmailCodeResult{}, errors.New("backendclient: confirm email code: challenge_id must not be empty")
	}
	body, status, err := c.doWithHeaders(ctx, http.MethodPost, c.baseURL+"/api/v1/public/auth/confirm-email-code", "", input, nil)
	if err != nil {
		return ConfirmEmailCodeResult{}, fmt.Errorf("backendclient: confirm email code: %w", err)
	}
	switch {
	case status == http.StatusOK:
		var result ConfirmEmailCodeResult
		if err := decodeStrictJSON(body, &result); err != nil {
			return ConfirmEmailCodeResult{}, fmt.Errorf("backendclient: confirm email code: decode success response: %w", err)
		}
		if strings.TrimSpace(result.DeviceSessionID) == "" {
			return ConfirmEmailCodeResult{}, errors.New("backendclient: confirm email code: device_session_id must not be empty")
		}
		return result, nil
	case status >= 400 && status <= 599:
		authErr, decodeErr := decodeAuthError(status, body)
		if decodeErr != nil {
			return ConfirmEmailCodeResult{}, fmt.Errorf("backendclient: confirm email code: %w", decodeErr)
		}
		return ConfirmEmailCodeResult{}, authErr
	default:
		return ConfirmEmailCodeResult{}, fmt.Errorf("backendclient: confirm email code: unexpected HTTP status %d", status)
	}
}

// resolvePreferredLanguage returns a non-empty Accept-Language value or
// the empty string when input is unset; downstream HTTP request helpers
// drop the header on empty values.
func resolvePreferredLanguage(preferred string) string {
	return strings.TrimSpace(preferred)
}

type authErrorEnvelope struct {
	Error *authErrorBody `json:"error"`
}

type authErrorBody struct {
	Code    string `json:"code"`
	Message string `json:"message"`
}

func decodeAuthError(statusCode int, payload []byte) (*AuthError, error) {
	var envelope authErrorEnvelope
	if err := decodeStrictJSON(payload, &envelope); err != nil {
		return nil, fmt.Errorf("decode error response: %w", err)
	}
	if envelope.Error == nil {
		return nil, errors.New("decode error response: missing error object")
	}
	return &AuthError{
		StatusCode: statusCode,
		Code:       envelope.Error.Code,
		Message:    envelope.Error.Message,
	}, nil
}
