package config

import (
	"errors"
	"sort"
	"time"

	"github.com/google/uuid"
	"github.com/rcourtman/pulse-go-rewrite/internal/auth"
)

// Canonical API token scope strings.
const (
	ScopeWildcard        = "*"
	ScopeMonitoringRead  = "monitoring:read"
	ScopeMonitoringWrite = "monitoring:write"
	ScopeDockerReport    = "docker:report"
	ScopeDockerManage    = "docker:manage"
	ScopeHostReport      = "host-agent:report"
	ScopeHostManage      = "host-agent:manage"
	ScopeSettingsRead    = "settings:read"
	ScopeSettingsWrite   = "settings:write"
)

// AllKnownScopes enumerates scopes recognized by the backend (excluding the wildcard sentinel).
var AllKnownScopes = []string{
	ScopeMonitoringRead,
	ScopeMonitoringWrite,
	ScopeDockerReport,
	ScopeDockerManage,
	ScopeHostReport,
	ScopeHostManage,
	ScopeSettingsRead,
	ScopeSettingsWrite,
}

var scopeLookup = func() map[string]struct{} {
	lookup := make(map[string]struct{}, len(AllKnownScopes))
	for _, scope := range AllKnownScopes {
		lookup[scope] = struct{}{}
	}
	return lookup
}()

// ErrInvalidToken is returned when a token value is empty or malformed.
var ErrInvalidToken = errors.New("invalid API token")

// APITokenRecord stores hashed token metadata.
type APITokenRecord struct {
	ID         string     `json:"id"`
	Name       string     `json:"name"`
	Hash       string     `json:"hash"`
	Prefix     string     `json:"prefix,omitempty"`
	Suffix     string     `json:"suffix,omitempty"`
	CreatedAt  time.Time  `json:"createdAt"`
	LastUsedAt *time.Time `json:"lastUsedAt,omitempty"`
	Scopes     []string   `json:"scopes,omitempty"`
}

// ensureScopes normalizes the scope slice, applying legacy defaults.
func (r *APITokenRecord) ensureScopes() {
	if len(r.Scopes) == 0 {
		r.Scopes = []string{ScopeWildcard}
		return
	}

	// Copy to avoid shared underlying slice if this record is reused.
	scopes := make([]string, len(r.Scopes))
	copy(scopes, r.Scopes)
	r.Scopes = scopes
}

// Clone returns a copy of the record with duplicated pointer fields.
func (r *APITokenRecord) Clone() APITokenRecord {
	clone := *r
	if r.LastUsedAt != nil {
		t := *r.LastUsedAt
		clone.LastUsedAt = &t
	}
	clone.ensureScopes()
	return clone
}

// NewAPITokenRecord constructs a metadata record from the provided raw token.
func NewAPITokenRecord(rawToken, name string, scopes []string) (*APITokenRecord, error) {
	if rawToken == "" {
		return nil, ErrInvalidToken
	}

	now := time.Now().UTC()
	record := &APITokenRecord{
		ID:        uuid.NewString(),
		Name:      name,
		Hash:      auth.HashAPIToken(rawToken),
		Prefix:    tokenPrefix(rawToken),
		Suffix:    tokenSuffix(rawToken),
		CreatedAt: now,
		Scopes:    normalizeScopes(scopes),
	}
	return record, nil
}

// NewHashedAPITokenRecord constructs a record from an already hashed token.
func NewHashedAPITokenRecord(hashedToken, name string, createdAt time.Time, scopes []string) (*APITokenRecord, error) {
	if hashedToken == "" {
		return nil, ErrInvalidToken
	}
	if createdAt.IsZero() {
		createdAt = time.Now().UTC()
	}

	return &APITokenRecord{
		ID:        uuid.NewString(),
		Name:      name,
		Hash:      hashedToken,
		Prefix:    tokenPrefix(hashedToken),
		Suffix:    tokenSuffix(hashedToken),
		CreatedAt: createdAt,
		Scopes:    normalizeScopes(scopes),
	}, nil
}

// tokenPrefix returns the first six characters suitable for hints.
func tokenPrefix(value string) string {
	if len(value) >= 6 {
		return value[:6]
	}
	return value
}

// tokenSuffix returns the last four characters suitable for hints.
func tokenSuffix(value string) string {
	if len(value) >= 4 {
		return value[len(value)-4:]
	}
	return value
}

// HasAPITokens reports whether any API tokens are configured.
func (c *Config) HasAPITokens() bool {
	return len(c.APITokens) > 0
}

// APITokenCount returns the number of configured tokens.
func (c *Config) APITokenCount() int {
	return len(c.APITokens)
}

// ActiveAPITokenHashes returns all stored token hashes.
func (c *Config) ActiveAPITokenHashes() []string {
	hashes := make([]string, 0, len(c.APITokens))
	for _, record := range c.APITokens {
		if record.Hash != "" {
			hashes = append(hashes, record.Hash)
		}
	}
	return hashes
}

// HasAPITokenHash returns true when the hash already exists.
func (c *Config) HasAPITokenHash(hash string) bool {
	for _, record := range c.APITokens {
		if record.Hash == hash {
			return true
		}
	}
	return false
}

// PrimaryAPITokenHash returns the newest token hash, if any.
func (c *Config) PrimaryAPITokenHash() string {
	if len(c.APITokens) == 0 {
		return ""
	}
	return c.APITokens[0].Hash
}

// PrimaryAPITokenHint provides a human-friendly token hint for UI display.
func (c *Config) PrimaryAPITokenHint() string {
	if len(c.APITokens) == 0 {
		return ""
	}
	token := c.APITokens[0]
	if token.Prefix != "" && token.Suffix != "" {
		return token.Prefix + "..." + token.Suffix
	}
	if len(token.Hash) >= 8 {
		return token.Hash[:4] + "..." + token.Hash[len(token.Hash)-4:]
	}
	return ""
}

// ValidateAPIToken compares the raw token against stored hashes and updates metadata.
func (c *Config) ValidateAPIToken(rawToken string) (*APITokenRecord, bool) {
	if rawToken == "" {
		return nil, false
	}

	for idx, record := range c.APITokens {
		if auth.CompareAPIToken(rawToken, record.Hash) {
			now := time.Now().UTC()
			c.APITokens[idx].LastUsedAt = &now
			c.APITokens[idx].ensureScopes()
			return &c.APITokens[idx], true
		}
	}
	return nil, false
}

// UpsertAPIToken inserts or replaces a record by ID.
func (c *Config) UpsertAPIToken(record APITokenRecord) {
	record.ensureScopes()
	for idx, existing := range c.APITokens {
		if existing.ID == record.ID {
			c.APITokens[idx] = record
			c.SortAPITokens()
			return
		}
	}
	c.APITokens = append(c.APITokens, record)
	c.SortAPITokens()
}

// RemoveAPIToken removes a token by ID.
func (c *Config) RemoveAPIToken(id string) bool {
	for idx, record := range c.APITokens {
		if record.ID == id {
			c.APITokens = append(c.APITokens[:idx], c.APITokens[idx+1:]...)
			return true
		}
	}
	return false
}

// SortAPITokens keeps tokens ordered newest-first and syncs the legacy APIToken field.
func (c *Config) SortAPITokens() {
	for i := range c.APITokens {
		c.APITokens[i].ensureScopes()
	}
	sort.SliceStable(c.APITokens, func(i, j int) bool {
		return c.APITokens[i].CreatedAt.After(c.APITokens[j].CreatedAt)
	})

	if len(c.APITokens) > 0 {
		c.APIToken = c.APITokens[0].Hash
		c.APITokenEnabled = true
	} else {
		c.APIToken = ""
	}
}

// normalizeScopes applies defaults and returns a safe copy of the input slice.
func normalizeScopes(scopes []string) []string {
	if len(scopes) == 0 {
		return []string{ScopeWildcard}
	}
	result := make([]string, len(scopes))
	copy(result, scopes)
	return result
}

// HasScope reports whether the record grants the requested scope or wildcard access.
func (r *APITokenRecord) HasScope(scope string) bool {
	if scope == "" {
		return true
	}
	r.ensureScopes()
	for _, candidate := range r.Scopes {
		if candidate == ScopeWildcard || candidate == scope {
			return true
		}
	}
	return false
}

// IsKnownScope reports whether the provided string matches a supported scope identifier.
func IsKnownScope(scope string) bool {
	if scope == ScopeWildcard {
		return true
	}
	_, ok := scopeLookup[scope]
	return ok
}
