package service

import (
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"github.com/tidbcloud/aisupport/services/oncall/db"
	"github.com/tidbcloud/aisupport/services/oncall/models"
	"strings"
	"time"
)

// Scopes granted to service (machine-to-machine) API keys.
const (
	ScopeOncallRead  = "oncall:read"  // Read on-call schedule / current on-call.
	ScopeOncallWrite = "oncall:write" // Mutate schedules / overrides.
)

// GenerateSecureKey generates a random secure API key string.
func GenerateSecureKey() (string, error) {
	bytes := make([]byte, 32)
	if _, err := rand.Read(bytes); err != nil {
		return "", err
	}
	return fmt.Sprintf("oc_v2_%s", hex.EncodeToString(bytes)), nil
}

// HashKey returns the hex-encoded sha256 of an API key. Only the hash is stored;
// the plaintext is shown to the creator exactly once.
func HashKey(key string) string {
	sum := sha256.Sum256([]byte(strings.TrimSpace(key)))
	return hex.EncodeToString(sum[:])
}

// keyPrefixOf returns a short, non-secret display hint for a key (e.g. oc_v2_1a2b3c4d).
func keyPrefixOf(key string) string {
	key = strings.TrimSpace(key)
	if len(key) > 14 {
		return key[:14]
	}
	return key
}

// ScopeContains reports whether a space-separated scope string grants the wanted scope.
func ScopeContains(scopes string, want string) bool {
	for _, s := range strings.Fields(scopes) {
		if s == want {
			return true
		}
	}
	return false
}

// CreateApiKey creates a new API key and returns the one-time plaintext key
// alongside the stored record. The plaintext is never persisted (only its hash).
//
// typ should be models.ApiKeyTypeUser or models.ApiKeyTypeService. For user keys
// scopes are ignored (they inherit the owner's permissions); for service keys
// scopes define what the key may do.
func CreateApiKey(email string, name string, typ string, scopes string) (string, models.ApiKey, error) {
	email = strings.ToLower(strings.TrimSpace(email))
	if typ != models.ApiKeyTypeService {
		typ = models.ApiKeyTypeUser
	}
	if typ == models.ApiKeyTypeUser {
		scopes = "" // user keys are not scope-restricted
	}

	plaintext, err := GenerateSecureKey()
	if err != nil {
		return "", models.ApiKey{}, err
	}

	apiKey := models.ApiKey{
		KeyHash:   HashKey(plaintext),
		KeyPrefix: keyPrefixOf(plaintext),
		Email:     email,
		Name:      name,
		Type:      typ,
		Scopes:    strings.TrimSpace(scopes),
		CreatedAt: time.Now(),
	}

	if err := db.DB.Create(&apiKey).Error; err != nil {
		return "", models.ApiKey{}, err
	}

	return plaintext, apiKey, nil
}

// FindApiKeyByPlaintext looks up a key by hashing the presented plaintext.
func FindApiKeyByPlaintext(plaintext string) (models.ApiKey, error) {
	var apiKey models.ApiKey
	if err := db.DB.Where("key_hash = ?", HashKey(plaintext)).First(&apiKey).Error; err != nil {
		return models.ApiKey{}, err
	}
	return apiKey, nil
}

// ListApiKeys retrieves all API keys for the given user email. The hash is never
// serialized (json:"-"); callers see only the non-secret KeyPrefix.
func ListApiKeys(email string) ([]models.ApiKey, error) {
	email = strings.ToLower(strings.TrimSpace(email))
	var keys []models.ApiKey
	if err := db.DB.Where("email = ?", email).Order("created_at DESC").Find(&keys).Error; err != nil {
		return nil, err
	}
	return keys, nil
}

// DeleteApiKey deletes an API key belonging to the user.
func DeleteApiKey(email string, keyID uint) error {
	email = strings.ToLower(strings.TrimSpace(email))
	// Ensure the key exists and belongs to the user
	var apiKey models.ApiKey
	if err := db.DB.Where("id = ? AND email = ?", keyID, email).First(&apiKey).Error; err != nil {
		return fmt.Errorf("API key not found or unauthorized")
	}

	return db.DB.Delete(&apiKey).Error
}
