package subscription

import (
	"time"

	"cloud.o-forge.io/core/oc-lib/models/common/pricing"
	"cloud.o-forge.io/core/oc-lib/models/utils"
	"cloud.o-forge.io/core/oc-lib/tools"
)

type SubscriptionStatus int

const (
	SUBSCRIPTION_PENDING   SubscriptionStatus = iota // en attente de premier paiement
	SUBSCRIPTION_TRIAL                               // période d'essai
	SUBSCRIPTION_ACTIVE                              // actif
	SUBSCRIPTION_PAUSED                              // suspendu temporairement
	SUBSCRIPTION_CANCELLED                           // résilié par l'utilisateur
	SUBSCRIPTION_EXPIRED                             // date de fin dépassée
)

func (s SubscriptionStatus) String() string {
	return [...]string{"pending", "trial", "active", "paused", "cancelled", "expired"}[s]
}

func SubscriptionStatusList() []SubscriptionStatus {
	return []SubscriptionStatus{SUBSCRIPTION_PENDING, SUBSCRIPTION_TRIAL, SUBSCRIPTION_ACTIVE, SUBSCRIPTION_PAUSED, SUBSCRIPTION_CANCELLED, SUBSCRIPTION_EXPIRED}
}

// SubscriptionItem représente un élément d'un abonnement (ressource louée).
type SubscriptionItem struct {
	ResourceID   string         `json:"resource_id" bson:"resource_id"`
	ResourceType tools.DataType `json:"resource_type" bson:"resource_type"`
	Quantity     int            `json:"quantity" bson:"quantity"`
	UnitPrice    float64        `json:"unit_price" bson:"unit_price"`
}

// Subscription représente un abonnement mensuel ou annuel à des ressources.
type Subscription struct {
	utils.AbstractObject
	SubscriberPeerID string             `json:"subscriber_peer_id" bson:"subscriber_peer_id" validate:"required"`
	ProviderPeerID   string             `json:"provider_peer_id,omitempty" bson:"provider_peer_id,omitempty"`
	Status           SubscriptionStatus `json:"status" bson:"status"`
	PlanType         pricing.PaymentType `json:"plan_type" bson:"plan_type"` // PAY_EVERY_MONTH ou PAY_EVERY_YEAR
	Items            []*SubscriptionItem `json:"items,omitempty" bson:"items,omitempty"`
	Amount           float64             `json:"amount" bson:"amount"`
	Currency         string              `json:"currency" bson:"currency" default:"EUR"`
	StartDate        time.Time           `json:"start_date" bson:"start_date"`
	EndDate          *time.Time          `json:"end_date,omitempty" bson:"end_date,omitempty"`
	NextBillingDate  time.Time           `json:"next_billing_date" bson:"next_billing_date"`
	AutoRenew        bool                `json:"auto_renew" bson:"auto_renew" default:"true"`
	TrialEndDate     *time.Time          `json:"trial_end_date,omitempty" bson:"trial_end_date,omitempty"`
	DiscountIDs      []string            `json:"discount_ids,omitempty" bson:"discount_ids,omitempty"`
	CancelledAt      *time.Time          `json:"cancelled_at,omitempty" bson:"cancelled_at,omitempty"`
	CancelReason     string              `json:"cancel_reason,omitempty" bson:"cancel_reason,omitempty"`
}

// newSubscription est le constructeur interne commun.
func newSubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string, plan pricing.PaymentType, nextBilling time.Time) *Subscription {
	now := time.Now().UTC()
	return &Subscription{
		SubscriberPeerID: subscriberPeerID,
		ProviderPeerID:   providerPeerID,
		Status:           SUBSCRIPTION_PENDING,
		PlanType:         plan,
		Items:            items,
		Amount:           amount,
		Currency:         currency,
		StartDate:        now,
		NextBillingDate:  nextBilling,
		AutoRenew:        true,
	}
}

// NewWeeklySubscription crée un abonnement hebdomadaire.
func NewWeeklySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription {
	now := time.Now().UTC()
	return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_WEEK, now.AddDate(0, 0, 7))
}

// NewMonthlySubscription crée un abonnement mensuel.
func NewMonthlySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription {
	now := time.Now().UTC()
	return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_MONTH, now.AddDate(0, 1, 0))
}

// NewYearlySubscription crée un abonnement annuel.
func NewYearlySubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string) *Subscription {
	now := time.Now().UTC()
	return newSubscription(subscriberPeerID, providerPeerID, items, amount, currency, pricing.PAY_EVERY_YEAR, now.AddDate(1, 0, 0))
}

// NewTrialSubscription crée un abonnement mensuel avec période d'essai.
func NewTrialSubscription(subscriberPeerID, providerPeerID string, items []*SubscriptionItem, amount float64, currency string, trialDays int) *Subscription {
	now := time.Now().UTC()
	trialEnd := now.AddDate(0, 0, trialDays)
	s := NewMonthlySubscription(subscriberPeerID, providerPeerID, items, amount, currency)
	s.Status = SUBSCRIPTION_TRIAL
	s.TrialEndDate = &trialEnd
	s.NextBillingDate = trialEnd
	return s
}

// Activate passe l'abonnement au statut actif (après premier paiement).
func (s *Subscription) Activate() {
	s.Status = SUBSCRIPTION_ACTIVE
}

// Pause suspend l'abonnement.
func (s *Subscription) Pause() {
	if s.Status == SUBSCRIPTION_ACTIVE {
		s.Status = SUBSCRIPTION_PAUSED
	}
}

// Resume réactive un abonnement suspendu.
func (s *Subscription) Resume() {
	if s.Status == SUBSCRIPTION_PAUSED {
		s.Status = SUBSCRIPTION_ACTIVE
	}
}

// Cancel résilie l'abonnement.
func (s *Subscription) Cancel(reason string) {
	now := time.Now().UTC()
	s.Status = SUBSCRIPTION_CANCELLED
	s.CancelledAt = &now
	s.CancelReason = reason
	s.AutoRenew = false
}

// Renew avance la prochaine date de facturation d'une période.
func (s *Subscription) Renew() {
	switch s.PlanType {
	case pricing.PAY_EVERY_WEEK:
		s.NextBillingDate = s.NextBillingDate.AddDate(0, 0, 7)
	case pricing.PAY_EVERY_MONTH:
		s.NextBillingDate = s.NextBillingDate.AddDate(0, 1, 0)
	case pricing.PAY_EVERY_YEAR:
		s.NextBillingDate = s.NextBillingDate.AddDate(1, 0, 0)
	}
}

// IsExpired vérifie si l'abonnement a dépassé sa date de fin.
func (s *Subscription) IsExpired() bool {
	if s.EndDate == nil {
		return false
	}
	return time.Now().UTC().After(*s.EndDate)
}

// IsBillingDue vérifie si la prochaine échéance est atteinte.
func (s *Subscription) IsBillingDue() bool {
	return s.Status == SUBSCRIPTION_ACTIVE && !time.Now().UTC().Before(s.NextBillingDate)
}

// IsInTrial vérifie si l'abonnement est en période d'essai.
func (s *Subscription) IsInTrial() bool {
	return s.Status == SUBSCRIPTION_TRIAL && s.TrialEndDate != nil && time.Now().UTC().Before(*s.TrialEndDate)
}

// ComputeAmount recalcule le montant total depuis les items.
func (s *Subscription) ComputeAmount() float64 {
	total := 0.0
	for _, item := range s.Items {
		total += item.UnitPrice * float64(item.Quantity)
	}
	s.Amount = total
	return total
}

func (s *Subscription) GetAccessor(request *tools.APIRequest) utils.Accessor {
	return NewAccessor(request)
}

func (s *Subscription) StoreDraftDefault() {
	s.IsDraft = true
}

func (s *Subscription) CanUpdate(set utils.DBObject) (bool, utils.DBObject) {
	incoming := set.(*Subscription)
	if !s.IsDraft && s.Status != incoming.Status {
		return true, &Subscription{
			Status:       incoming.Status,
			AutoRenew:    incoming.AutoRenew,
			CancelReason: incoming.CancelReason,
		}
	}
	return s.IsDraft, set
}

func (s *Subscription) CanDelete() bool {
	return s.IsDraft || s.Status == SUBSCRIPTION_CANCELLED
}
