package queries

import (
	"time"

	"github.com/fiskerinc/cloud-services/pkg/common"
	"github.com/fiskerinc/cloud-services/pkg/validator"

	"github.com/go-pg/pg/v10/orm"
	"github.com/google/uuid"
	"github.com/pkg/errors"
)

type SubscriptionsInterface interface {
	Delete(req *SubscriptionDeleteRequest) (orm.Result, error)
	Insert(subtype *common.Subscription) (orm.Result, error)
	Update(subtype *common.Subscription) (orm.Result, error)
	Select(filter *common.Subscription) ([]common.Subscription, error)
	Load(subtype *common.Subscription) error
	Count(filter *common.Subscription) (int, error)
	Create(subtype *common.SubscriptionType, carToDriver *common.CarToDriver) (*common.Subscription, error)
}

type Subscriptions struct {
	QueryBase
}

// Select returns list of drivers
func (s *Subscriptions) Select(filter *common.Subscription) ([]common.Subscription, error) {
	subs := []common.Subscription{}
	query := s.GetDBConn().Model(&subs)

	s.selectFilter(query, filter)

	err := query.Select()

	return subs, errors.WithStack(err)
}

func (s *Subscriptions) selectFilter(query *orm.Query, filter *common.Subscription) {
	if filter.ID != 0 {
		query.Where("id = ?", filter.ID)
	}

	if filter.SubscriptionTypeID != uuid.Nil {
		query.Where("subscription_type_id = ?", filter.SubscriptionTypeID)
	}

	if filter.CarToDriverID != 0 {
		query.Where("car_to_driver_id = ?", filter.CarToDriverID)
	}

	if filter.Name != "" {
		query.Where("name = ?", filter.Name)
	}

	if filter.Name != "" {
		query.Where("destination = ?", filter.Destination)
	}
}

func (s *Subscriptions) Insert(subtype *common.Subscription) (orm.Result, error) {
	return s.insert(subtype)
}

func (s *Subscriptions) Update(subtype *common.Subscription) (orm.Result, error) {
	err := s.validateSubID(subtype)
	if err != nil {
		return nil, err
	}

	return s.GetDBConn().Model(subtype).Column("subscription_type_id", "car_to_driver_id", "name", "destination", "expires").WherePK().Update()
}

func (s *Subscriptions) Delete(req *SubscriptionDeleteRequest) (orm.Result, error) {
	if req.ID > 0 {
		return s.deleteByID(&common.Subscription{ID: req.ID})
	}

	return s.deleteRequest(req)
}

func (s *Subscriptions) deleteByID(sub *common.Subscription) (orm.Result, error) {
	err := s.validateSubID(sub)
	if err != nil {
		return nil, err
	}

	return s.resultWithStack(s.GetDBConn().Model(sub).WherePK().Delete())
}

func (s *Subscriptions) deleteRequest(req *SubscriptionDeleteRequest) (orm.Result, error) {
	err := validator.ValidateStruct(req)
	if err != nil {
		return nil, errors.WithStack(err)
	}

	return s.resultWithStack(s.GetDBConn().Model((*common.Subscription)(nil)).Exec(`DELETE FROM "subscriptions" AS "subs" WHERE (subs.id IN (SELECT subs2.id FROM "subscriptions" AS "subs2" JOIN car_to_drivers AS c ON (c.id = subs2.car_to_driver_id) WHERE (c.vin = ? AND subs2.name = ?)))`, req.VIN, req.Name))
}

func (s *Subscriptions) Load(sub *common.Subscription) error {
	query := s.GetDBConn().Model(sub)

	if sub.ID != 0 {
		query.WherePK()
	} else if sub.SubscriptionTypeID != uuid.Nil && sub.CarToDriverID != 0 {
		query.Where("subscription_type_id = ?subscription_type_id AND driver_id = ?car_to_driver_id")
	} else {
		return errors.New("no id or subscription_type_id, driver_id")
	}

	return errors.WithStack(query.Select())
}

func (s *Subscriptions) Count(filter *common.Subscription) (int, error) {
	query := s.GetDBConn().Model((*common.Subscription)(nil))

	s.selectFilter(query, filter)

	count, err := query.Count()

	return count, errors.WithStack(err)
}

func (s *Subscriptions) validateSubID(subtype *common.Subscription) error {
	if subtype.ID == 0 {
		return errors.WithStack(&validator.FieldError{
			ErrorMsg: "ID required",
		})
	}

	return nil
}

func (s *Subscriptions) Create(subtype *common.SubscriptionType, carToDriver *common.CarToDriver) (*common.Subscription, error) {
	sub := common.Subscription{
		Name:               subtype.Name,
		Destination:        subtype.Destination,
		CarToDriverID:      carToDriver.ID,
		SubscriptionTypeID: subtype.ID,
		Expires:            time.Now().Add(subtype.GetDuration()),
	}

	_, err := s.Insert(&sub)
	if err != nil {
		return nil, err
	}

	return &sub, nil
}

type SubscriptionDeleteRequest struct {
	ID   int64  `json:"id"`
	Name string `json:"name" validate:"required,max=256"`
	VIN  string `json:"vin" validate:"required,vin"`
}
