package auth

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"sync"
	"time"

	"github.com/lukaszraczylo/gohoarder/pkg/errors"
	"golang.org/x/crypto/bcrypt"
)

// Manager handles authentication and authorization
type Manager struct {
	keys map[string]*APIKey
	mu   sync.RWMutex
}

// APIKey represents an API key
type APIKey struct {
	ID          string
	Name        string
	HashedKey   string
	Role        Role
	CreatedAt   time.Time
	ExpiresAt   *time.Time
	LastUsedAt  time.Time
	Permissions []Permission
}

// Role represents user role
type Role string

const (
	RoleReadOnly  Role = "readonly"
	RoleReadWrite Role = "readwrite"
	RoleAdmin     Role = "admin"
)

// Permission represents a specific permission
type Permission string

const (
	PermissionReadPackage    Permission = "package:read"
	PermissionWritePackage   Permission = "package:write"
	PermissionDeletePackage  Permission = "package:delete"
	PermissionViewStats      Permission = "stats:view"
	PermissionManageKeys     Permission = "keys:manage"
	PermissionManageSettings Permission = "settings:manage"
	PermissionScanPackages   Permission = "scan:execute"
	PermissionManageBypasses Permission = "bypasses:manage"
)

// New creates a new authentication manager
func New() *Manager {
	return &Manager{
		keys: make(map[string]*APIKey),
	}
}

// GenerateAPIKey generates a new API key
func (m *Manager) GenerateAPIKey(name string, role Role, expiresIn *time.Duration) (*APIKey, string, error) {
	// Generate random key
	keyBytes := make([]byte, 32)
	if _, err := rand.Read(keyBytes); err != nil {
		return nil, "", errors.Wrap(err, errors.ErrCodeInternalServer, "failed to generate random key")
	}

	rawKey := base64.URLEncoding.EncodeToString(keyBytes)

	// Hash the key
	hashedKey, err := bcrypt.GenerateFromPassword([]byte(rawKey), bcrypt.DefaultCost)
	if err != nil {
		return nil, "", errors.Wrap(err, errors.ErrCodeInternalServer, "failed to hash key")
	}

	var expiresAt *time.Time
	if expiresIn != nil {
		t := time.Now().Add(*expiresIn)
		expiresAt = &t
	}

	apiKey := &APIKey{
		ID:          generateID(),
		Name:        name,
		HashedKey:   string(hashedKey),
		Role:        role,
		CreatedAt:   time.Now(),
		ExpiresAt:   expiresAt,
		Permissions: getPermissionsForRole(role),
	}

	m.mu.Lock()
	m.keys[apiKey.ID] = apiKey
	m.mu.Unlock()

	return apiKey, rawKey, nil
}

// ValidateAPIKey validates an API key and returns the associated key object
func (m *Manager) ValidateAPIKey(ctx context.Context, rawKey string) (*APIKey, error) {
	m.mu.RLock()
	defer m.mu.RUnlock()

	for _, apiKey := range m.keys {
		// Check if key is expired
		if apiKey.ExpiresAt != nil && time.Now().After(*apiKey.ExpiresAt) {
			continue
		}

		// Compare hashed key
		if err := bcrypt.CompareHashAndPassword([]byte(apiKey.HashedKey), []byte(rawKey)); err == nil {
			// Update last used
			apiKey.LastUsedAt = time.Now()
			return apiKey, nil
		}
	}

	return nil, errors.New(errors.ErrCodeUnauthorized, "invalid API key")
}

// RevokeAPIKey revokes an API key
func (m *Manager) RevokeAPIKey(keyID string) error {
	m.mu.Lock()
	defer m.mu.Unlock()

	if _, exists := m.keys[keyID]; !exists {
		return errors.NotFound("API key not found")
	}

	delete(m.keys, keyID)
	return nil
}

// ListAPIKeys lists all API keys
func (m *Manager) ListAPIKeys() []*APIKey {
	m.mu.RLock()
	defer m.mu.RUnlock()

	keys := make([]*APIKey, 0, len(m.keys))
	for _, key := range m.keys {
		keys = append(keys, key)
	}
	return keys
}

// HasPermission checks if an API key has a specific permission
func (k *APIKey) HasPermission(permission Permission) bool {
	for _, p := range k.Permissions {
		if p == permission {
			return true
		}
	}
	return false
}

// getPermissionsForRole returns permissions for a role
func getPermissionsForRole(role Role) []Permission {
	switch role {
	case RoleReadOnly:
		return []Permission{
			PermissionReadPackage,
			PermissionViewStats,
		}
	case RoleReadWrite:
		return []Permission{
			PermissionReadPackage,
			PermissionWritePackage,
			PermissionViewStats,
		}
	case RoleAdmin:
		return []Permission{
			PermissionReadPackage,
			PermissionWritePackage,
			PermissionDeletePackage,
			PermissionViewStats,
			PermissionManageKeys,
			PermissionManageSettings,
			PermissionScanPackages,
			PermissionManageBypasses,
		}
	default:
		return []Permission{}
	}
}

// generateID generates a unique ID
func generateID() string {
	b := make([]byte, 16)
	_, _ = rand.Read(b) // #nosec G104 -- Rand read always succeeds
	return base64.URLEncoding.EncodeToString(b)
}
