package authorizationv1

import (
	"context"
	"strings"
	"unicode"
	"unicode/utf8"
)

func ValidateBeginRequest(ctx context.Context, request BeginRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if err := validateInstallation(request.AppInstallationID); err != nil {
		return err
	}
	// Client ID, redirect URI, and response type are untrusted OAuth protocol
	// inputs. Preserve even their absent form for the durable owner so it can
	// classify the protocol error and decide whether a redirect is trusted.
	// Contract validation owns only the neutral wire shape and bounds.
	if !optional(request.ClientID, MaxClientIDBytes) || !optional(request.RedirectURI, MaxURIBytes) ||
		!optional(request.ResponseType, 64) || !optional(request.Scope, MaxScopeBytes) ||
		!optional(request.State, MaxStateBytes) || !optional(request.CodeChallenge, MaxPKCEChallengeBytes) ||
		!optional(request.CodeChallengeMethod, 32) || !required(request.Issuer, MaxIssuerBytes) ||
		!optional(request.Resource, MaxResourceBytes) || (!request.ResourcePresent && request.Resource != "") {
		return invalid(InvalidComponentFieldValue)
	}
	return nil
}

func ValidateBeginResponse(response BeginResponse) error {
	if response.ProtocolError != nil {
		if response.RequestHandle != "" || response.ExpiresAtUnixSecond != 0 {
			return internal()
		}
		return validateAuthorizationProtocolError(*response.ProtocolError)
	}
	if !required(response.RequestHandle, MaxHandleBytes) || response.ExpiresAtUnixSecond <= 0 {
		return internal()
	}
	return nil
}

func ValidateCompleteRequest(ctx context.Context, request CompleteRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if err := validateInstallation(request.AppInstallationID); err != nil {
		return err
	}
	if !required(request.RequestHandle, MaxHandleBytes) {
		return invalid(InvalidComponentFieldValue)
	}
	return nil
}

func ValidateCompleteResponse(response CompleteResponse) error {
	if response.ProtocolError != nil {
		if response.AuthorizationCodeIssued || response.RedirectURI != "" || response.State != "" {
			return internal()
		}
		return validateAuthorizationProtocolError(*response.ProtocolError)
	}
	if !response.AuthorizationCodeIssued || !required(response.RedirectURI, MaxURIBytes) ||
		!optional(response.State, MaxStateBytes) {
		return internal()
	}
	return nil
}

func ValidateExchangeRequest(ctx context.Context, request ExchangeRequest) error {
	if err := validateContext(ctx); err != nil {
		return err
	}
	if err := validateInstallation(request.AppInstallationID); err != nil {
		return err
	}
	// Missing client/redirect values remain OAuth protocol inputs; the Runtime
	// owner, not the transport mapper, returns invalid_client/invalid_request.
	if !optional(request.ClientID, MaxClientIDBytes) || !required(request.Issuer, MaxIssuerBytes) ||
		!optional(request.RedirectURI, MaxURIBytes) || !optional(request.RequestedScope, MaxScopeBytes) ||
		!optional(request.Resource, MaxResourceBytes) || (!request.ResourcePresent && request.Resource != "") {
		return invalid(InvalidComponentFieldValue)
	}
	switch request.GrantType {
	case GrantTypeAuthorizationCode:
	case GrantTypeRefreshToken:
		if request.RedirectURI != "" || request.ResourcePresent {
			return invalid(InvalidComponentOperation)
		}
	case GrantTypeClientCredentials:
		if request.RedirectURI != "" {
			return invalid(InvalidComponentOperation)
		}
	default:
		return invalid(InvalidComponentOperation)
	}
	return nil
}

func ValidateExchangeResponse(response ExchangeResponse) error {
	if response.ProtocolError != nil {
		if response.Token != nil {
			return internal()
		}
		return validateExchangeProtocolError(*response.ProtocolError)
	}
	if response.Token == nil || !required(response.Token.TokenType, MaxTokenTypeBytes) ||
		response.Token.ExpiresInSecond <= 0 || !response.Token.AccessTokenIssued ||
		!optional(response.Token.Scope, MaxScopeBytes) {
		return internal()
	}
	return nil
}

// ValidateExchangeResponseForRequest closes grant-specific issuance invariants
// that cannot be proven from the response shape alone.
func ValidateExchangeResponseForRequest(request ExchangeRequest, response ExchangeResponse) error {
	if err := ValidateExchangeResponse(response); err != nil {
		return err
	}
	switch request.GrantType {
	case GrantTypeAuthorizationCode, GrantTypeRefreshToken:
	case GrantTypeClientCredentials:
		if response.Token != nil && response.Token.RefreshTokenIssued {
			return internal()
		}
	default:
		return internal()
	}
	return nil
}

func validateAuthorizationProtocolError(value ProtocolError) error {
	switch value.Code {
	case "invalid_client", "invalid_request", "invalid_scope", "invalid_target", "unsupported_response_type",
		"unauthorized_client", "access_denied", "server_error", "temporarily_unavailable":
	default:
		return internal()
	}
	return validateProtocolErrorShape(value, true)
}

func validateExchangeProtocolError(value ProtocolError) error {
	switch value.Code {
	case "invalid_client", "invalid_request", "invalid_scope", "invalid_grant", "invalid_target",
		"unsupported_grant_type", "unauthorized_client":
	default:
		return internal()
	}
	return validateProtocolErrorShape(value, false)
}

func validateProtocolErrorShape(value ProtocolError, allowRedirect bool) error {
	if !required(value.SafeDescription, MaxDescriptionBytes) ||
		!optional(value.TrustedRedirectURI, MaxURIBytes) || !optional(value.State, MaxStateBytes) ||
		(value.TrustedRedirectURI == "" && value.State != "") ||
		(!allowRedirect && (value.TrustedRedirectURI != "" || value.State != "")) {
		return internal()
	}
	return nil
}

func validateContext(ctx context.Context) error {
	if ctx == nil {
		return invalid(InvalidComponentDeadline)
	}
	if err := ctx.Err(); err != nil {
		if err == context.DeadlineExceeded {
			return &Failure{Category: FailureDeadlineExceeded}
		}
		return &Failure{Category: FailureCanceled}
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline)
	}
	return nil
}

func validateInstallation(value string) error {
	if required(value, MaxIdentifierBytes) {
		return nil
	}
	if len(value) > MaxIdentifierBytes {
		return &Failure{Category: FailureResourceExhausted, LimitKind: LimitAppInstallationID, Maximum: MaxIdentifierBytes}
	}
	return invalid(InvalidComponentAppInstallationID)
}

func required(value string, maximum int) bool { return value != "" && optional(value, maximum) }
func optional(value string, maximum int) bool {
	if len(value) > maximum || !utf8.ValidString(value) || strings.TrimSpace(value) != value {
		return false
	}
	for _, r := range value {
		if unicode.IsControl(r) {
			return false
		}
	}
	return true
}
func invalid(component InvalidComponent) error {
	return &Failure{Category: FailureInvalidRequest, InvalidComponent: component}
}
func internal() error { return &Failure{Category: FailureInternal} }
