package services

import (
	"encoding/json"
	"fmt"
	"math"
	"strconv"
	"strings"
	"time"

	"go.etcd.io/bbolt"
	"warpbox.dev/backend/libs/config"
)

var (
	settingsBucket = []byte("settings")
	usageBucket    = []byte("usage")
)

var settingsKey = []byte("upload_policy")

type UploadPolicySettings struct {
	AnonymousUploadsEnabled bool    `json:"anonymousUploadsEnabled"`
	AnonymousMaxUploadMB    float64 `json:"anonymousMaxUploadMb"`
	AnonymousDailyUploadMB  float64 `json:"anonymousDailyUploadMb"`
	UserDailyUploadMB       float64 `json:"userDailyUploadMb"`
	DefaultUserStorageMB    float64 `json:"defaultUserStorageMb"`
	UsageRetentionDays      int     `json:"usageRetentionDays"`
	LocalStorageMaxGB       float64 `json:"localStorageMaxGb"`
	AnonymousMaxDays        int     `json:"anonymousMaxDays"`
	UserMaxDays             int     `json:"userMaxDays"`
	AnonymousDailyBoxes     int     `json:"anonymousDailyBoxes"`
	UserDailyBoxes          int     `json:"userDailyBoxes"`
	AnonymousActiveBoxes    int     `json:"anonymousActiveBoxes"`
	UserActiveBoxes         int     `json:"userActiveBoxes"`
	ShortWindowRequests     int     `json:"shortWindowRequests"`
	ShortWindowSeconds      int     `json:"shortWindowSeconds"`
	AnonymousStorageBackend string  `json:"anonymousStorageBackend"`
	UserStorageBackend      string  `json:"userStorageBackend"`
	ResumableUploadsEnabled bool    `json:"resumableUploadsEnabled"`
	ResumableChunkSizeMB    float64 `json:"resumableChunkSizeMb"`
	ResumableRetentionHours int     `json:"resumableRetentionHours"`
	ResumableChunkMode      string  `json:"resumableChunkMode"`
	ResumableChunkPath      string  `json:"resumableChunkPath"`
}

type UsageRecord struct {
	Key           string    `json:"key"`
	SubjectType   string    `json:"subjectType"`
	Subject       string    `json:"subject"`
	Date          string    `json:"date"`
	UploadedBytes int64     `json:"uploadedBytes"`
	UploadedBoxes int       `json:"uploadedBoxes"`
	RequestCount  int       `json:"requestCount"`
	UpdatedAt     time.Time `json:"updatedAt"`
}

type EffectiveUploadPolicy struct {
	MaxUploadMB      float64
	DailyUploadMB    float64
	StorageQuotaMB   float64
	MaxDays          int
	DailyBoxes       int
	ActiveBoxes      int
	ShortRequests    int
	ShortWindow      time.Duration
	StorageBackendID string
	StorageQuotaSet  bool
}

type SettingsService struct {
	db       *bbolt.DB
	defaults UploadPolicySettings
}

func NewSettingsService(db *bbolt.DB, defaults config.SettingsDefaults) (*SettingsService, error) {
	service := &SettingsService{
		db: db,
		defaults: UploadPolicySettings{
			AnonymousUploadsEnabled: defaults.AnonymousUploadsEnabled,
			AnonymousMaxUploadMB:    defaults.AnonymousMaxUploadMB,
			AnonymousDailyUploadMB:  defaults.AnonymousDailyUploadMB,
			UserDailyUploadMB:       defaults.UserDailyUploadMB,
			DefaultUserStorageMB:    defaults.DefaultUserStorageMB,
			UsageRetentionDays:      defaults.UsageRetentionDays,
			LocalStorageMaxGB:       defaults.LocalStorageMaxGB,
			AnonymousMaxDays:        defaults.AnonymousMaxDays,
			UserMaxDays:             defaults.UserMaxDays,
			AnonymousDailyBoxes:     defaults.AnonymousDailyBoxes,
			UserDailyBoxes:          defaults.UserDailyBoxes,
			AnonymousActiveBoxes:    defaults.AnonymousActiveBoxes,
			UserActiveBoxes:         defaults.UserActiveBoxes,
			ShortWindowRequests:     defaults.ShortWindowRequests,
			ShortWindowSeconds:      defaults.ShortWindowSeconds,
			AnonymousStorageBackend: defaults.AnonymousStorageBackend,
			UserStorageBackend:      defaults.UserStorageBackend,
			ResumableUploadsEnabled: defaults.ResumableUploadsEnabled,
			ResumableChunkSizeMB:    defaults.ResumableChunkSizeMB,
			ResumableRetentionHours: defaults.ResumableRetentionHours,
			ResumableChunkMode:      defaults.ResumableChunkMode,
			ResumableChunkPath:      defaults.ResumableChunkPath,
		},
	}
	service.defaults = service.withBuiltinDefaultGaps(service.defaults)
	if err := service.validate(service.defaults); err != nil {
		return nil, err
	}
	err := db.Update(func(tx *bbolt.Tx) error {
		for _, bucket := range [][]byte{settingsBucket, usageBucket} {
			if _, err := tx.CreateBucketIfNotExists(bucket); err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return service, nil
}

func (s *SettingsService) withBuiltinDefaultGaps(settings UploadPolicySettings) UploadPolicySettings {
	if settings.LocalStorageMaxGB <= 0 {
		settings.LocalStorageMaxGB = 100
	}
	if settings.AnonymousMaxDays <= 0 {
		settings.AnonymousMaxDays = 30
	}
	if settings.UserMaxDays <= 0 {
		settings.UserMaxDays = 90
	}
	if settings.AnonymousDailyBoxes <= 0 {
		settings.AnonymousDailyBoxes = 100
	}
	if settings.UserDailyBoxes <= 0 {
		settings.UserDailyBoxes = 250
	}
	if settings.AnonymousActiveBoxes <= 0 {
		settings.AnonymousActiveBoxes = 500
	}
	if settings.UserActiveBoxes <= 0 {
		settings.UserActiveBoxes = 1000
	}
	if settings.ShortWindowRequests <= 0 {
		settings.ShortWindowRequests = 60
	}
	if settings.ShortWindowSeconds <= 0 {
		settings.ShortWindowSeconds = 60
	}
	if strings.TrimSpace(settings.AnonymousStorageBackend) == "" {
		settings.AnonymousStorageBackend = StorageBackendLocal
	}
	if strings.TrimSpace(settings.UserStorageBackend) == "" {
		settings.UserStorageBackend = StorageBackendLocal
	}
	if settings.ResumableChunkSizeMB <= 0 {
		settings.ResumableChunkSizeMB = 8
	}
	if settings.ResumableRetentionHours <= 0 {
		settings.ResumableRetentionHours = 24
	}
	if strings.TrimSpace(settings.ResumableChunkMode) == "" {
		settings.ResumableChunkMode = "same"
	}
	return settings
}

func (s *SettingsService) UploadPolicy() (UploadPolicySettings, error) {
	settings := s.defaults
	err := s.db.View(func(tx *bbolt.Tx) error {
		data := tx.Bucket(settingsBucket).Get(settingsKey)
		if data == nil {
			return nil
		}
		if err := json.Unmarshal(data, &settings); err != nil {
			return err
		}
		var raw map[string]json.RawMessage
		if err := json.Unmarshal(data, &raw); err != nil {
			return err
		}
		if _, ok := raw["resumableUploadsEnabled"]; !ok {
			settings.ResumableUploadsEnabled = s.defaults.ResumableUploadsEnabled
		}
		settings = s.withDefaultGaps(settings)
		return nil
	})
	if err != nil {
		return UploadPolicySettings{}, err
	}
	if err := s.validate(settings); err != nil {
		return UploadPolicySettings{}, err
	}
	return settings, nil
}

func (s *SettingsService) withDefaultGaps(settings UploadPolicySettings) UploadPolicySettings {
	if settings.AnonymousMaxUploadMB == 0 {
		settings.AnonymousMaxUploadMB = s.defaults.AnonymousMaxUploadMB
	}
	if settings.AnonymousDailyUploadMB == 0 {
		settings.AnonymousDailyUploadMB = s.defaults.AnonymousDailyUploadMB
	}
	if settings.UserDailyUploadMB == 0 {
		settings.UserDailyUploadMB = s.defaults.UserDailyUploadMB
	}
	if settings.DefaultUserStorageMB <= 0 {
		settings.DefaultUserStorageMB = s.defaults.DefaultUserStorageMB
	}
	if settings.UsageRetentionDays <= 0 {
		settings.UsageRetentionDays = s.defaults.UsageRetentionDays
	}
	if settings.LocalStorageMaxGB <= 0 {
		settings.LocalStorageMaxGB = s.defaults.LocalStorageMaxGB
	}
	if settings.AnonymousMaxDays <= 0 {
		settings.AnonymousMaxDays = s.defaults.AnonymousMaxDays
	}
	if settings.UserMaxDays <= 0 {
		settings.UserMaxDays = s.defaults.UserMaxDays
	}
	if settings.AnonymousDailyBoxes <= 0 {
		settings.AnonymousDailyBoxes = s.defaults.AnonymousDailyBoxes
	}
	if settings.UserDailyBoxes <= 0 {
		settings.UserDailyBoxes = s.defaults.UserDailyBoxes
	}
	if settings.AnonymousActiveBoxes <= 0 {
		settings.AnonymousActiveBoxes = s.defaults.AnonymousActiveBoxes
	}
	if settings.UserActiveBoxes <= 0 {
		settings.UserActiveBoxes = s.defaults.UserActiveBoxes
	}
	if settings.ShortWindowRequests <= 0 {
		settings.ShortWindowRequests = s.defaults.ShortWindowRequests
	}
	if settings.ShortWindowSeconds <= 0 {
		settings.ShortWindowSeconds = s.defaults.ShortWindowSeconds
	}
	if strings.TrimSpace(settings.AnonymousStorageBackend) == "" {
		settings.AnonymousStorageBackend = s.defaults.AnonymousStorageBackend
	}
	if strings.TrimSpace(settings.UserStorageBackend) == "" {
		settings.UserStorageBackend = s.defaults.UserStorageBackend
	}
	if settings.ResumableChunkSizeMB <= 0 {
		settings.ResumableChunkSizeMB = s.defaults.ResumableChunkSizeMB
	}
	if settings.ResumableRetentionHours <= 0 {
		settings.ResumableRetentionHours = s.defaults.ResumableRetentionHours
	}
	if strings.TrimSpace(settings.ResumableChunkMode) == "" {
		settings.ResumableChunkMode = s.defaults.ResumableChunkMode
	}
	return settings
}

func (s *SettingsService) UpdateUploadPolicy(settings UploadPolicySettings) error {
	if err := s.validate(settings); err != nil {
		return err
	}
	data, err := json.Marshal(settings)
	if err != nil {
		return err
	}
	return s.db.Update(func(tx *bbolt.Tx) error {
		return tx.Bucket(settingsBucket).Put(settingsKey, data)
	})
}

func (s *SettingsService) ResetStorageBackend(backendID string) (bool, bool, error) {
	backendID = strings.TrimSpace(backendID)
	if backendID == "" || backendID == StorageBackendLocal {
		return false, false, nil
	}
	settings, err := s.UploadPolicy()
	if err != nil {
		return false, false, err
	}
	resetAnonymous := settings.AnonymousStorageBackend == backendID
	resetUser := settings.UserStorageBackend == backendID
	if !resetAnonymous && !resetUser {
		return false, false, nil
	}
	if resetAnonymous {
		settings.AnonymousStorageBackend = StorageBackendLocal
	}
	if resetUser {
		settings.UserStorageBackend = StorageBackendLocal
	}
	return resetAnonymous, resetUser, s.UpdateUploadPolicy(settings)
}

func (s *SettingsService) Usage(subjectType, subject string, now time.Time) (UsageRecord, error) {
	key := usageKey(subjectType, subject, now)
	var record UsageRecord
	err := s.db.View(func(tx *bbolt.Tx) error {
		data := tx.Bucket(usageBucket).Get([]byte(key))
		if data == nil {
			record = UsageRecord{Key: key, SubjectType: subjectType, Subject: subject, Date: usageDate(now)}
			return nil
		}
		return json.Unmarshal(data, &record)
	})
	return record, err
}

func (s *SettingsService) AddUsage(subjectType, subject string, bytes int64, now time.Time) error {
	return s.AddUploadUsage(subjectType, subject, bytes, 0, now)
}

func (s *SettingsService) AddUploadUsage(subjectType, subject string, bytes int64, boxes int, now time.Time) error {
	if bytes <= 0 {
		bytes = 0
	}
	if boxes < 0 {
		boxes = 0
	}
	if bytes == 0 && boxes == 0 {
		return nil
	}
	key := usageKey(subjectType, subject, now)
	return s.db.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(usageBucket)
		record := UsageRecord{Key: key, SubjectType: subjectType, Subject: subject, Date: usageDate(now)}
		data := bucket.Get([]byte(key))
		if data != nil {
			if err := json.Unmarshal(data, &record); err != nil {
				return err
			}
		}
		record.UploadedBytes += bytes
		record.UploadedBoxes += boxes
		record.UpdatedAt = now.UTC()
		next, err := json.Marshal(record)
		if err != nil {
			return err
		}
		return bucket.Put([]byte(key), next)
	})
}

func (s *SettingsService) EffectivePolicyForAnonymous(settings UploadPolicySettings) EffectiveUploadPolicy {
	return EffectiveUploadPolicy{
		MaxUploadMB:      settings.AnonymousMaxUploadMB,
		DailyUploadMB:    settings.AnonymousDailyUploadMB,
		MaxDays:          settings.AnonymousMaxDays,
		DailyBoxes:       settings.AnonymousDailyBoxes,
		ActiveBoxes:      settings.AnonymousActiveBoxes,
		ShortRequests:    settings.ShortWindowRequests,
		ShortWindow:      time.Duration(settings.ShortWindowSeconds) * time.Second,
		StorageBackendID: normalizeBackendID(settings.AnonymousStorageBackend),
	}
}

func (s *SettingsService) EffectivePolicyForUser(settings UploadPolicySettings, user User) EffectiveUploadPolicy {
	policy := EffectiveUploadPolicy{
		MaxUploadMB:      0,
		DailyUploadMB:    settings.UserDailyUploadMB,
		StorageQuotaMB:   settings.DefaultUserStorageMB,
		MaxDays:          settings.UserMaxDays,
		DailyBoxes:       settings.UserDailyBoxes,
		ActiveBoxes:      settings.UserActiveBoxes,
		ShortRequests:    settings.ShortWindowRequests,
		ShortWindow:      time.Duration(settings.ShortWindowSeconds) * time.Second,
		StorageBackendID: normalizeBackendID(settings.UserStorageBackend),
		StorageQuotaSet:  true,
	}
	if user.StorageQuotaMB != nil {
		policy.StorageQuotaMB = *user.StorageQuotaMB
	}
	if user.Policy.MaxUploadMB != nil {
		policy.MaxUploadMB = *user.Policy.MaxUploadMB
	}
	if user.Policy.DailyUploadMB != nil {
		policy.DailyUploadMB = *user.Policy.DailyUploadMB
	}
	if user.Policy.StorageQuotaMB != nil {
		policy.StorageQuotaMB = *user.Policy.StorageQuotaMB
		policy.StorageQuotaSet = *user.Policy.StorageQuotaMB > 0
	}
	if user.Policy.MaxDays != nil {
		policy.MaxDays = *user.Policy.MaxDays
	}
	if user.Policy.DailyBoxes != nil {
		policy.DailyBoxes = *user.Policy.DailyBoxes
	}
	if user.Policy.ActiveBoxes != nil {
		policy.ActiveBoxes = *user.Policy.ActiveBoxes
	}
	if user.Policy.ShortWindowRequests != nil {
		policy.ShortRequests = *user.Policy.ShortWindowRequests
	}
	if user.Policy.StorageBackendID != nil {
		policy.StorageBackendID = normalizeBackendID(*user.Policy.StorageBackendID)
	}
	return policy
}

func (s *SettingsService) CleanupUsage(now time.Time, retentionDays int) error {
	if retentionDays <= 0 {
		return fmt.Errorf("usage retention days must be positive")
	}
	cutoff := now.UTC().AddDate(0, 0, -retentionDays)
	return s.db.Update(func(tx *bbolt.Tx) error {
		bucket := tx.Bucket(usageBucket)
		return bucket.ForEach(func(key, value []byte) error {
			var record UsageRecord
			if err := json.Unmarshal(value, &record); err != nil {
				return err
			}
			date, err := time.Parse("2006-01-02", record.Date)
			if err != nil || date.Before(cutoff) {
				return bucket.Delete(key)
			}
			return nil
		})
	})
}

func (s *SettingsService) UsageForUser(userID string, now time.Time) (UsageRecord, error) {
	return s.Usage("user", userID, now)
}

func (s *SettingsService) UsageForIP(ip string, now time.Time) (UsageRecord, error) {
	return s.Usage("ip", ip, now)
}

func (s *SettingsService) validate(settings UploadPolicySettings) error {
	if settings.AnonymousMaxUploadMB < 0 && settings.AnonymousMaxUploadMB != -1 || settings.AnonymousMaxUploadMB == 0 {
		return fmt.Errorf("anonymous max upload must be positive or -1 for unlimited")
	}
	if settings.AnonymousDailyUploadMB < 0 && settings.AnonymousDailyUploadMB != -1 || settings.AnonymousDailyUploadMB == 0 {
		return fmt.Errorf("anonymous daily upload must be positive or -1 for unlimited")
	}
	if settings.UserDailyUploadMB < 0 && settings.UserDailyUploadMB != -1 || settings.UserDailyUploadMB == 0 {
		return fmt.Errorf("user daily upload must be positive or -1 for unlimited")
	}
	if settings.DefaultUserStorageMB <= 0 {
		return fmt.Errorf("default user storage must be positive")
	}
	if settings.UsageRetentionDays <= 0 {
		return fmt.Errorf("usage retention days must be positive")
	}
	if settings.LocalStorageMaxGB <= 0 {
		return fmt.Errorf("local storage max must be positive")
	}
	if settings.AnonymousMaxDays <= 0 || settings.UserMaxDays <= 0 {
		return fmt.Errorf("expiration limits must be positive")
	}
	if settings.AnonymousDailyBoxes <= 0 || settings.UserDailyBoxes <= 0 {
		return fmt.Errorf("daily box limits must be positive")
	}
	if settings.AnonymousActiveBoxes <= 0 || settings.UserActiveBoxes <= 0 {
		return fmt.Errorf("active box limits must be positive")
	}
	if settings.ShortWindowRequests <= 0 || settings.ShortWindowSeconds <= 0 {
		return fmt.Errorf("short-window rate limits must be positive")
	}
	if settings.ResumableChunkSizeMB <= 0 {
		return fmt.Errorf("resumable chunk size must be positive")
	}
	if settings.ResumableRetentionHours <= 0 {
		return fmt.Errorf("resumable retention must be positive")
	}
	if settings.ResumableChunkMode != "same" && settings.ResumableChunkMode != "custom" {
		return fmt.Errorf("resumable chunk storage mode is invalid")
	}
	if settings.ResumableChunkMode == "custom" && strings.TrimSpace(settings.ResumableChunkPath) == "" {
		return fmt.Errorf("custom resumable chunk path is required")
	}
	return nil
}

func ParseMegabytesValue(value string) (float64, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, fmt.Errorf("megabyte value is required")
	}
	value = strings.TrimSuffix(value, "MB")
	value = strings.TrimSuffix(value, "Mb")
	value = strings.TrimSuffix(value, "mb")
	value = strings.TrimSpace(value)
	parsed, err := strconv.ParseFloat(value, 64)
	if err != nil {
		return 0, err
	}
	if parsed <= 0 {
		return 0, fmt.Errorf("megabyte value must be positive")
	}
	return parsed, nil
}

func ParseMegabytesLimitValue(value string) (float64, error) {
	parsed, err := parseMegabytesNumber(value)
	if err != nil {
		return 0, err
	}
	if parsed == -1 {
		return -1, nil
	}
	if parsed <= 0 {
		return 0, fmt.Errorf("megabyte value must be positive or -1 for unlimited")
	}
	return parsed, nil
}

func parseMegabytesNumber(value string) (float64, error) {
	value = strings.TrimSpace(value)
	if value == "" {
		return 0, fmt.Errorf("megabyte value is required")
	}
	value = strings.TrimSuffix(value, "MB")
	value = strings.TrimSuffix(value, "Mb")
	value = strings.TrimSuffix(value, "mb")
	value = strings.TrimSpace(value)
	return strconv.ParseFloat(value, 64)
}

func MegabytesToBytes(value float64) int64 {
	return int64(value * 1024 * 1024)
}

func GigabytesToBytes(value float64) int64 {
	return int64(value * 1024 * 1024 * 1024)
}

func FormatMegabytesFromBytes(value int64) string {
	mb := float64(value) / 1024 / 1024
	mb = math.Round(mb*100) / 100
	return FormatMegabytesLabel(mb)
}

func FormatMegabytesLabel(value float64) string {
	if value < 0 {
		return "unlimited"
	}
	return strconv.FormatFloat(value, 'f', -1, 64) + " MB"
}

func usageKey(subjectType, subject string, now time.Time) string {
	return subjectType + ":" + subject + ":" + usageDate(now)
}

func usageDate(now time.Time) string {
	return now.UTC().Format("2006-01-02")
}

func normalizeBackendID(id string) string {
	id = strings.TrimSpace(id)
	if id == "" {
		return StorageBackendLocal
	}
	return id
}
