package commerce

import (
	"context"
	"errors"
	"fmt"
	"time"

	"gorm.io/gorm"
)

type InstanceLifecycleSummary struct {
	AutoRenewed     int `json:"auto_renewed"`
	AutoRenewFailed int `json:"auto_renew_failed"`
	Reminded        int `json:"reminded"`
	Suspended       int `json:"suspended"`
	Deleted         int `json:"deleted"`
	Failed          int `json:"failed"`
}

func (s *Service) ProcessInstanceLifecycle(ctx context.Context, limit int) (InstanceLifecycleSummary, error) {
	if err := s.requireDB(); err != nil {
		return InstanceLifecycleSummary{}, err
	}
	if limit <= 0 {
		limit = 50
	}
	summary := InstanceLifecycleSummary{}
	renewed, renewFailed, err := s.processAutoRenewals(ctx, limit)
	if err != nil {
		return summary, err
	}
	summary.AutoRenewed = renewed
	summary.AutoRenewFailed = renewFailed
	reminded, err := s.sendExpirationReminders(ctx, limit)
	if err != nil {
		return summary, err
	}
	summary.Reminded = reminded
	suspended, deleted, failed, err := s.processExpiredInstances(ctx, limit)
	if err != nil {
		return summary, err
	}
	summary.Suspended = suspended
	summary.Deleted = deleted
	summary.Failed = failed
	return summary, nil
}

func (s *Service) processAutoRenewals(ctx context.Context, limit int) (int, int, error) {
	now := time.Now().UTC()
	window := now.Add(6 * time.Hour)
	var instances []Instance
	if err := s.db.WithContext(ctx).
		Where("auto_renew = ? AND status IN ? AND expires_at IS NOT NULL AND expires_at <= ?", 1, []string{"active", "suspended"}, window).
		Order("expires_at ASC, id ASC").
		Limit(limit).
		Find(&instances).Error; err != nil {
		return 0, 0, err
	}
	renewed := 0
	failed := 0
	for _, instance := range instances {
		if _, err := s.renewInstance(ctx, instance.UserID, instance.ID, 1, "auto_renew", instance.ExpiresAt); err != nil {
			if errors.Is(err, ErrAutoRenewalAlreadyProcessed) {
				continue
			}
			failed++
			_ = s.createLifecycleNotification(ctx, instance, "Auto renewal failed", fmt.Sprintf("Instance #%d auto renewal failed: %s", instance.ID, err.Error()), "warning")
			continue
		}
		renewed++
	}
	return renewed, failed, nil
}

func (s *Service) sendExpirationReminders(ctx context.Context, limit int) (int, error) {
	now := time.Now().UTC()
	until := now.Add(7 * 24 * time.Hour)
	var instances []Instance
	if err := s.db.WithContext(ctx).
		Where("status = ? AND expires_at IS NOT NULL AND expires_at > ? AND expires_at <= ?", "active", now, until).
		Order("expires_at ASC, id ASC").
		Limit(limit).
		Find(&instances).Error; err != nil {
		return 0, err
	}
	created := 0
	for _, instance := range instances {
		if instance.ExpiresAt == nil {
			continue
		}
		days := int(instance.ExpiresAt.Sub(now).Hours() / 24)
		if days != 0 && days != 1 && days != 3 && days != 7 {
			continue
		}
		title := fmt.Sprintf("Instance #%d expires in %d days", instance.ID, days)
		var count int64
		link := fmt.Sprintf("/control/server/%d", instance.ID)
		err := s.db.WithContext(ctx).Model(&Notification{}).
			Where("user_id = ? AND category = ? AND title = ? AND link = ?", instance.UserID, "instance", title, link).
			Count(&count).Error
		if err != nil {
			return created, err
		}
		if count > 0 {
			continue
		}
		if err := s.createLifecycleNotification(ctx, instance, title, fmt.Sprintf("Instance #%d expires at %s.", instance.ID, instance.ExpiresAt.Format(time.RFC3339)), "warning"); err != nil {
			return created, err
		}
		created++
	}
	return created, nil
}

func (s *Service) processExpiredInstances(ctx context.Context, limit int) (int, int, int, error) {
	now := time.Now().UTC()
	suspendAfter := s.intSetting(ctx, "lifecycle.expire_suspend_after_days", 0)
	deleteAfter := s.intSetting(ctx, "lifecycle.expire_delete_after_days", 7)
	if deleteAfter < suspendAfter {
		deleteAfter = suspendAfter
	}
	suspendCutoff := now.AddDate(0, 0, -suspendAfter)
	deleteCutoff := now.AddDate(0, 0, -deleteAfter)

	var instances []Instance
	if err := s.db.WithContext(ctx).
		Where("expires_at IS NOT NULL AND ((status = ? AND expires_at <= ?) OR (status = ? AND suspend_type = ? AND expires_at <= ?))",
			"active", suspendCutoff, "suspended", "due", deleteCutoff).
		Order("expires_at ASC, id ASC").
		Limit(limit).
		Find(&instances).Error; err != nil {
		return 0, 0, 0, err
	}

	suspended := 0
	deleted := 0
	failed := 0
	for _, instance := range instances {
		if instance.ExpiresAt == nil {
			continue
		}
		if instance.Status == "suspended" && instance.SuspendType != nil && *instance.SuspendType == "due" && !instance.ExpiresAt.After(deleteCutoff) {
			if err := s.lifecycleDeleteInstance(ctx, instance); err != nil {
				failed++
				continue
			}
			deleted++
			continue
		}
		if instance.Status == "active" && !instance.ExpiresAt.After(suspendCutoff) {
			if err := s.lifecycleSuspendInstance(ctx, instance); err != nil {
				failed++
				continue
			}
			suspended++
		}
	}
	return suspended, deleted, failed, nil
}

func (s *Service) lifecycleSuspendInstance(ctx context.Context, instance Instance) error {
	product, _, err := s.productAndGroup(ctx, instance.ProductID)
	if err != nil {
		return err
	}
	if product != nil && product.APIType == "plugin" && product.APIIdentifier != nil {
		if !s.pluginActionAllowedOrMissing(*product, "suspend") {
			return BadRequest("provision plugin does not support action: suspend")
		}
		if _, err := s.submitPluginManageActionWithAudit(ctx, instance, *product, "suspend", map[string]any{"type": "due"}, "lifecycle", nil, nil); err != nil {
			_ = s.createLifecycleNotification(ctx, instance, "Instance suspend failed", fmt.Sprintf("Instance #%d expired suspend failed: %s", instance.ID, err.Error()), "warning")
			return err
		}
	} else if instance.UpstreamID != nil {
		_, err := s.submitUpstreamActionWithAudit(ctx, instance, "instance_suspend", "lifecycle", nil, nil, "POST", fmt.Sprintf("/clouds/%d/suspend", *instance.UpstreamID), map[string]any{"type": "due", "free_gpu": 1}, "instance expired auto suspend")
		if err != nil {
			_ = s.createLifecycleNotification(ctx, instance, "Instance suspend failed", fmt.Sprintf("Instance #%d expired suspend failed: %s", instance.ID, err.Error()), "warning")
			return err
		}
	} else {
		_, err := s.recordUnsupportedResourceAction(ctx, instance, "instance_suspend", "lifecycle", nil, nil, "POST", "/suspend", map[string]any{"type": "due"}, "实例没有可执行暂停的插件或上游资源")
		return err
	}
	suspendType := "due"
	if err := s.db.WithContext(ctx).Model(&Instance{}).Where("id = ?", instance.ID).Updates(map[string]any{
		"status":       "suspended",
		"suspend_type": suspendType,
	}).Error; err != nil {
		return err
	}
	return s.createLifecycleNotification(ctx, instance, "Instance suspended", fmt.Sprintf("Instance #%d has been suspended because it expired.", instance.ID), "warning")
}

func (s *Service) lifecycleDeleteInstance(ctx context.Context, instance Instance) error {
	if _, err := s.submitTerminateUpstreamAction(ctx, instance, nil, nil, map[string]any{"source": "lifecycle"}); err != nil {
		_ = s.createLifecycleNotification(ctx, instance, "Instance release failed", fmt.Sprintf("Instance #%d expired release failed: %s", instance.ID, err.Error()), "warning")
		return err
	}
	now := time.Now().UTC()
	if err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		txService := s.withDB(tx)
		if err := tx.Model(&Instance{}).Where("id = ?", instance.ID).Updates(map[string]any{
			"status":        "terminated",
			"suspend_type":  nil,
			"terminated_at": now,
		}).Error; err != nil {
			return err
		}
		_, err := txService.RestockReleasedInstanceTx(ctx, instance.ID, "lifecycle", "instance expired release", "")
		return err
	}); err != nil {
		return err
	}
	return s.createLifecycleNotification(ctx, instance, "Instance released", fmt.Sprintf("Instance #%d has been released because it expired.", instance.ID), "warning")
}

func (s *Service) createLifecycleNotification(ctx context.Context, instance Instance, title string, content string, level string) error {
	channels, err := s.notificationChannels(ctx, instance.UserID, "instance")
	if err != nil {
		return err
	}
	if !channels.InApp && !channels.Email {
		return nil
	}
	link := fmt.Sprintf("/control/server/%d", instance.ID)
	senderName := "Instances"
	actionLabel := "View"
	if channels.InApp {
		notification := Notification{
			UserID:      instance.UserID,
			Category:    "instance",
			Level:       level,
			SenderName:  &senderName,
			SenderType:  "instance",
			Title:       title,
			Content:     content,
			Link:        &link,
			ActionLabel: &actionLabel,
			ActionURL:   &link,
			IsRead:      false,
		}
		if err := s.db.WithContext(ctx).Create(&notification).Error; err != nil {
			return err
		}
	}
	if channels.Email {
		if err := SendInstanceLifecycleEmail(ctx, s.db, instance, title, content, s.plugins); err != nil && s.logger != nil {
			s.logger.Warn("instance lifecycle email skipped", "instance_id", instance.ID, "title", title, "error", err)
		}
	}
	return nil
}

func (s *Service) notificationEnabled(ctx context.Context, userID int64, category string) (bool, error) {
	channels, err := s.notificationChannels(ctx, userID, category)
	return channels.InApp, err
}

type notificationChannelsValue struct {
	InApp bool
	Email bool
	SMS   bool
}

func (s *Service) notificationChannels(ctx context.Context, userID int64, category string) (notificationChannelsValue, error) {
	var pref NotificationPreference
	err := s.db.WithContext(ctx).Where("user_id = ? AND category = ?", userID, category).First(&pref).Error
	if err == nil {
		return notificationChannelsValue{InApp: pref.InAppEnabled, Email: pref.EmailEnabled, SMS: pref.SMSEnabled}, nil
	}
	if errorsIsRecordNotFound(err) || err == gorm.ErrRecordNotFound {
		return notificationChannelsValue{InApp: true}, nil
	}
	return notificationChannelsValue{}, err
}
