package gotify

import (
	"strconv"
	"strings"
	"time"
)

// DefaultValidator provides the default implementation of Validator.
type DefaultValidator struct{}

// Validator handles input validation for the Gotify service.
type Validator interface {
	ValidateMessage(message string) error
	ValidateServiceInitialized(config *Config) error
	ValidatePriority(priority int) error
	ValidateDate(date string) (string, error)
	ValidateToken(token string) bool
}

const (
	// TokenLength defines the expected length of a legacy Gotify token.
	//
	// NOTE: Legacy tokens generated by Gotify < 3.0.0 are exactly 15 characters
	// and start with 'A'. Gotify 3.0.0+ introduced a new token format that is
	// longer (approximately 50 characters) and starts with "gtfy" followed by
	// an identifier and a dot, e.g. "gtfya." for application tokens.
	// See: https://github.com/gotify/server/blob/master/auth/token.go
	//
	// This constant is retained for validating the legacy token format.
	TokenLength = 15
	// TokenChars specifies the valid characters for a legacy Gotify token.
	//
	// NOTE: This character set applies only to legacy tokens (Gotify < 3.0.0).
	// New format tokens start with "gtfy" and are not restricted to this set.
	// See: https://github.com/gotify/server/blob/master/auth/token.go
	TokenChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_"
	// MinEnhancedTokenLength is the minimum length for a Gotify 3.0.0+ token.
	//
	// NOTE: Enhanced tokens start with "gtfy" + ident + ".", followed by
	// base64url-encoded key material. An ed25519 public key is 32 bytes,
	// which encodes to 43 base64url characters. With the 6-character prefix
	// ("gtfya." / "gtfyc."), the minimum valid token length is 49.
	MinEnhancedTokenLength = 49
)

// ValidateDate validates and converts the provided date string to RFC3339 format.
// It attempts parsing multiple formats in order of preference: RFC3339, RFC3339 without timezone,
// Unix timestamp (seconds), and basic date-time formats.
// Returns the converted date string in RFC3339 format and nil error on success,
// or empty string and an error if all parsing attempts fail.
func (v *DefaultValidator) ValidateDate(date string) (string, error) {
	if date == "" {
		return "", nil
	}

	// Try RFC3339
	if t, err := time.Parse(time.RFC3339, date); err == nil {
		return t.UTC().Format(time.RFC3339), nil
	}

	// Try RFC3339 without timezone
	if t, err := time.ParseInLocation("2006-01-02T15:04:05", date, time.UTC); err == nil {
		return t.UTC().Format(time.RFC3339), nil
	}

	// Try Unix timestamp seconds
	if unix, err := strconv.ParseInt(date, 10, 64); err == nil {
		t := time.Unix(unix, 0)

		return t.UTC().Format(time.RFC3339), nil
	}

	// Try basic date-time "2006-01-02 15:04:05"
	if t, err := time.ParseInLocation("2006-01-02 15:04:05", date, time.UTC); err == nil {
		return t.UTC().Format(time.RFC3339), nil
	}

	return "", ErrInvalidDate
}

// ValidateMessage checks if the message is not empty.
func (v *DefaultValidator) ValidateMessage(message string) error {
	if message == "" {
		return ErrEmptyMessage
	}

	return nil
}

// ValidatePriority checks if the priority is within the valid range (-2 to 10).
func (v *DefaultValidator) ValidatePriority(priority int) error {
	if priority < -2 || priority > 10 {
		return ErrInvalidPriority
	}

	return nil
}

// ValidateServiceInitialized checks if the service configuration is initialized.
func (v *DefaultValidator) ValidateServiceInitialized(config *Config) error {
	if config == nil {
		return ErrServiceNotInitialized
	}

	return nil
}

// ValidateToken checks if a Gotify token meets format requirements.
//
// This function accepts two token formats for backward compatibility:
//
//   - Legacy tokens (Gotify < 3.0.0): exactly 15 characters, starting with 'A',
//     containing only characters from TokenChars.
//   - New tokens (Gotify 3.0.0+): starting with "gtfy" followed by a single
//     identifier character and a dot, e.g. "gtfya." for application tokens
//     or "gtfyc." for client tokens.
//
// The legacy format is supported for backward compatibility but may be removed
// in a future release once Gotify 3.0.0+ is the minimum supported version.
// See: https://github.com/gotify/server/blob/master/auth/token.go
//
// NOTE(nicholas-fedor): Remove legacy token validation once Gotify 3.0.0+ is
// the minimum supported version.
func (v *DefaultValidator) ValidateToken(token string) bool {
	if token == "" {
		return false
	}

	// Accept new Gotify 3.0.0+ enhanced token format.
	// Format: "gtfy" + ident + "." + key material
	// e.g. "gtfya." for application tokens, "gtfyc." for client tokens.
	// See: https://github.com/gotify/server/blob/master/auth/token.go
	if len(token) >= 6 && token[:4] == "gtfy" && token[4] != '.' && token[5] == '.' {
		return len(token) >= MinEnhancedTokenLength
	}

	// Legacy Gotify < 3.0.0 token format: exactly 15 characters,
	// starting with 'A', containing only allowed characters.
	if len(token) == TokenLength && token[0] == 'A' {
		for _, c := range token {
			if !strings.ContainsRune(TokenChars, c) {
				return false
			}
		}

		return true
	}

	return false
}
