package billing

import (
	"errors"
	"fmt"
	"time"

	"github.com/pingcap/dbaas/billing2/billing_errors"
	"github.com/pingcap/dbaas/billing2/db"
	"github.com/pingcap/dbaas/billing2/db/entity"
	"github.com/pingcap/dbaas/billing2/forms"
	"github.com/pingcap/dbaas/billing2/utils/errorutils"
)

type SubscriptionModel struct{}

func subscriptionFromEntity(entity *entity.Subscription) *forms.Subscription {
	return &forms.Subscription{
		SubscriptionID: entity.ID,
		TenantID:       entity.TenantID,
		Status:         entity.Status,
		FilePath:       entity.FilePath,
		ContractEmail:  entity.ContractEmail,
		AcceptedUser:   entity.AcceptedUser,
		AcceptedDate:   entity.AcceptedDate,
	}
}

func (*SubscriptionModel) CreateSubscription(tenantID uint, subscription *forms.Subscription) (*forms.Subscription, error) {
	subscriptionEntity := &entity.Subscription{
		TenantID:      tenantID,
		Status:        entity.SubscriptionStatus_DRAFT,
		FilePath:      subscription.FilePath,
		ContractEmail: subscription.ContractEmail,
	}
	err := subscriptionEntity.Create(db.GetDB())
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return subscriptionFromEntity(subscriptionEntity), nil
}

func (*SubscriptionModel) ListSubscriptions(tenantID uint) ([]forms.Subscription, error) {
	subscriptions := []entity.Subscription{}
	err := entity.NewSubscriptionQuerySet(db.GetDB()).TenantIDEq(tenantID).OrderDescByCreatedAt().All(&subscriptions)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	infos := make([]forms.Subscription, len(subscriptions))
	for i, subscription := range subscriptions {
		infos[i] = *subscriptionFromEntity(&subscription)
	}
	return infos, nil
}

func (*SubscriptionModel) ListSubmitedSubscriptions(tenantID uint) ([]forms.Subscription, error) {
	subscriptions := []entity.Subscription{}
	err := entity.NewSubscriptionQuerySet(db.GetDB()).TenantIDEq(tenantID).StatusNotIn(entity.SubscriptionStatus_DRAFT).OrderDescByCreatedAt().All(&subscriptions)
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	infos := make([]forms.Subscription, len(subscriptions))
	for i, subscription := range subscriptions {
		infos[i] = *subscriptionFromEntity(&subscription)
	}
	return infos, nil
}

func (*SubscriptionModel) GetSubscription(subscriptionID uint) (*forms.Subscription, error) {
	subscriptionEntity := &entity.Subscription{}
	err := errorutils.WrapSQLError(entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).One(subscriptionEntity), fmt.Sprintf("Subscription %v", subscriptionID))
	if err != nil {
		return nil, err
	}
	return subscriptionFromEntity(subscriptionEntity), nil
}

func (m *SubscriptionModel) UpdateSubscription(subscriptionID uint, input *forms.Subscription) (*forms.Subscription, error) {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return nil, err
	}
	if subscription.Status == entity.SubscriptionStatus_ACCEPTED {
		return nil, billing_errors.NewInvalidInputError(errors.New("can not update accepted subscription"))
	}

	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).GetUpdater().SetContractEmail(input.ContractEmail).SetStatus(entity.SubscriptionStatus_DRAFT).SetFilePath(input.FilePath).Update()
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	return m.GetSubscription(subscriptionID)
}

func (m *SubscriptionModel) DeleteSubscription(subscriptionID uint) error {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return err
	}
	if subscription.Status != entity.SubscriptionStatus_DRAFT && subscription.Status != entity.SubscriptionStatus_PENDING {
		return billing_errors.NewInvalidInputError(errors.New("can not delete signed subscription"))
	}
	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).Delete()
	if err != nil {
		return billing_errors.NewSystemError(err)
	}

	return nil
}

func (m *SubscriptionModel) SubmitSubscription(subscriptionID uint) (*forms.Subscription, error) {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return nil, err
	}
	if subscription.Status != entity.SubscriptionStatus_DRAFT {
		return nil, billing_errors.NewInvalidInputError(errors.New("submit subscription status is not draft"))
	}
	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).GetUpdater().SetStatus(entity.SubscriptionStatus_PENDING).Update()
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	subscription.Status = entity.SubscriptionStatus_PENDING
	return subscription, nil
}

func (m *SubscriptionModel) AcceptSubscription(subscriptionID uint, accept *forms.SubscriptionChange) (*forms.Subscription, error) {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return nil, err
	}
	if subscription.Status != entity.SubscriptionStatus_PENDING {
		return nil, billing_errors.NewInvalidInputError(errors.New("accept subscription status is not pending"))
	}

	acceptedTime := time.Now()

	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).GetUpdater().SetStatus(entity.SubscriptionStatus_ACCEPTED).SetAcceptedDate(&acceptedTime).SetAcceptedUser(&accept.ChangeUser).Update()
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	subscription.Status = entity.SubscriptionStatus_ACCEPTED
	subscription.AcceptedUser = &accept.ChangeUser
	subscription.AcceptedDate = &acceptedTime
	return subscription, nil
}

func (m *SubscriptionModel) RejectSubscription(subscriptionID uint, reject *forms.SubscriptionChange) (*forms.Subscription, error) {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return nil, err
	}
	if subscription.Status != entity.SubscriptionStatus_PENDING {
		return nil, billing_errors.NewInvalidInputError(errors.New("reject subscription status is not pending"))
	}

	rejectedTime := time.Now()

	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).GetUpdater().SetStatus(entity.SubscriptionStatus_REJECTED).SetRejectedDate(&rejectedTime).SetRejectedUser(&reject.ChangeUser).Update()
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	subscription.Status = entity.SubscriptionStatus_REJECTED
	subscription.RejectedUser = &reject.ChangeUser
	subscription.RejectedDate = &rejectedTime
	return subscription, nil
}

func (m *SubscriptionModel) SuspendSubscription(subscriptionID uint) (*forms.Subscription, error) {
	subscription, err := m.GetSubscription(subscriptionID)
	if err != nil {
		return nil, err
	}
	if subscription.Status != entity.SubscriptionStatus_ACCEPTED {
		return nil, billing_errors.NewInvalidInputError(errors.New("suspend subscription status is not accepted"))
	}

	err = entity.NewSubscriptionQuerySet(db.GetDB()).IDEq(subscriptionID).GetUpdater().SetStatus(entity.SubscriptionStatus_SUSPENDED).Update()
	if err != nil {
		return nil, billing_errors.NewSystemError(err)
	}
	subscription.Status = entity.SubscriptionStatus_SUSPENDED
	return subscription, nil
}
