// Copyright 2019 Yunion
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package models

import (
	"context"
	"net/http"
	"time"

	"yunion.io/x/pkg/errors"

	api "yunion.io/x/onecloud/pkg/apis/notify"
	"yunion.io/x/onecloud/pkg/appsrv"
	"yunion.io/x/onecloud/pkg/cloudcommon/db"
	"yunion.io/x/onecloud/pkg/mcclient"
)

var ReceiverNotificationManager *SReceiverNotificationManager

func init() {
	db.InitManager(func() {
		ReceiverNotificationManager = &SReceiverNotificationManager{
			SJointResourceBaseManager: db.NewJointResourceBaseManager(
				SReceiverNotification{},
				"receivernotification_tbl",
				"receivernotification",
				"receivernotifications",
				NotificationManager,
				ReceiverManager,
			),
		}
		ReceiverNotificationManager.SetVirtualObject(ReceiverNotificationManager)
		ReceiverNotificationManager.TableSpec().AddIndex(true, "receiver_id", "contact", "notification_id")
	})
}

type SReceiverNotificationManager struct {
	db.SJointResourceBaseManager
}

const (
	ReceiverIdDefault = "default"
)

// +onecloud:swagger-gen-ignore
type SReceiverNotification struct {
	db.SJointResourceBase

	ReceiverID     string `width:"128" charset:"ascii" nullable:"false" index:"true"`
	NotificationID string `width:"128" charset:"ascii" nullable:"false" index:"true"`
	// ignore if ReceiverID is not empty or default
	Contact      string `width:"128" index:"true"`
	ReceiverType string `width:"16"`
	SendAt       time.Time
	SendBy       string `width:"128"`
	Status       string `width:"36" charset:"ascii"`
	FailedReason string `width:"1024"`
	// minutes
	GroupTimes uint32
}

func (self *SReceiverNotificationManager) InitializeData() error {
	return dataCleaning(self.TableSpec().Name())
}

func (self *SReceiverNotification) GetReceiver() (*SReceiver, error) {
	recv, err := ReceiverManager.FetchById(self.ReceiverID)
	if err != nil {
		return nil, errors.Wrapf(err, "with id %s", self.ReceiverID)
	}
	return recv.(*SReceiver), nil
}

func (rnm *SReceiverNotificationManager) Create(ctx context.Context, userCred mcclient.TokenCredential, receiverID string, groupTimes uint32, notificationID string) (*SReceiverNotification, error) {
	rn := &SReceiverNotification{
		ReceiverID:     receiverID,
		NotificationID: notificationID,
		GroupTimes:     groupTimes,
		ReceiverType:   api.RECEIVER_TYPE_USER,
		Status:         api.RECEIVER_NOTIFICATION_RECEIVED,
		SendBy:         userCred.GetUserId(),
	}
	return rn, rnm.TableSpec().Insert(ctx, rn)
}

func (rnm *SReceiverNotificationManager) CreateRobot(ctx context.Context, userCred mcclient.TokenCredential, RobotID string, groupTimes uint32, notificationID string) (*SReceiverNotification, error) {
	rn := &SReceiverNotification{
		ReceiverID:     RobotID,
		NotificationID: notificationID,
		ReceiverType:   api.RECEIVER_TYPE_ROBOT,
		Status:         api.RECEIVER_NOTIFICATION_RECEIVED,
		SendBy:         userCred.GetUserId(),
		GroupTimes:     groupTimes,
	}
	return rn, rnm.TableSpec().Insert(ctx, rn)
}

func (rnm *SReceiverNotificationManager) GetMasterFieldName() string {
	return "notification_id"
}

func (rnm *SReceiverNotificationManager) GetSlaveFieldName() string {
	return "receiver_id"
}

func (rnm *SReceiverNotificationManager) CreateContact(ctx context.Context, userCred mcclient.TokenCredential, contact, notificationID string) (*SReceiverNotification, error) {
	rn := &SReceiverNotification{
		NotificationID: notificationID,
		ReceiverType:   api.RECEIVER_TYPE_CONTACT,
		Contact:        contact,
		Status:         api.RECEIVER_NOTIFICATION_RECEIVED,
		SendBy:         userCred.GetUserId(),
	}
	return rn, rnm.TableSpec().Insert(ctx, rn)
}

func (rnm *SReceiverNotificationManager) SetHandlerProcessTimeout(info *appsrv.SHandlerInfo, r *http.Request) time.Duration {
	if r.Method == http.MethodGet && len(r.URL.Query().Get("export_keys")) > 0 {
		return time.Hour * 2
	}
	return -time.Second
}

func (rn *SReceiverNotification) Receiver() (IReceiver, error) {
	switch rn.ReceiverType {
	case api.RECEIVER_TYPE_USER:
		return rn.receiver()
	case api.RECEIVER_TYPE_CONTACT:
		return &SContact{contact: rn.Contact}, nil
	case api.RECEIVER_TYPE_ROBOT:
		return rn.robot()
	default:
		// compatible
		if rn.ReceiverID != "" && rn.ReceiverID != ReceiverIdDefault {
			return rn.receiver()
		}
		return &SContact{contact: rn.Contact}, nil
	}
}

func (rn *SReceiverNotification) receiver() (*SReceiver, error) {
	q := ReceiverManager.Query().Equals("id", rn.ReceiverID)
	var receiver SReceiver
	err := q.First(&receiver)
	if err != nil {
		return nil, err
	}
	receiver.SetModelManager(ReceiverManager, &receiver)
	return &receiver, nil
}

func (rn *SReceiverNotification) robot() (*SRobot, error) {
	q := RobotManager.Query().Equals("id", rn.ReceiverID)
	var robot SRobot
	err := q.First(&robot)
	if err != nil {
		return nil, err
	}
	robot.SetModelManager(RobotManager, &robot)
	return &robot, nil
}

/*
func (rn *SReceiverNotification) Receiver() (IReceiver, error) {
	switch rn.ReceiverType {
	case api.RECEIVER_TYPE_USER:
		return rn.receiver()
	case api.RECEIVER_TYPE_CONTACT:
		return &SContact{contact: rn.Contact}, nil
	case api.RECEIVER_TYPE_ROBOT:
		return rn.robot()
	default:
		// compatible
		if rn.ReceiverID != "" && rn.ReceiverID != ReceiverIdDefault {
			return rn.receiver()
		}
		return &SContact{contact: rn.Contact}, nil
	}
}
*/

func (rn *SReceiverNotification) BeforeSend(ctx context.Context, sendTime time.Time) error {
	if sendTime.IsZero() {
		sendTime = time.Now()
	}
	_, err := db.Update(rn, func() error {
		rn.SendAt = sendTime
		rn.Status = api.RECEIVER_NOTIFICATION_SENT
		return nil
	})
	return err
}

func (rn *SReceiverNotification) AfterSend(ctx context.Context, success bool, reason string) error {
	_, err := db.Update(rn, func() error {
		if success {
			rn.Status = api.RECEIVER_NOTIFICATION_OK
		} else {
			rn.Status = api.RECEIVER_NOTIFICATION_FAIL
			rn.FailedReason = reason
		}
		return nil
	})
	return err
}

type IReceiver interface {
	IsRobot() bool
	IsReceiver() bool
	IsEnabled() bool
	GetDomainId() string
	IsEnabledContactType(string) (bool, error)
	IsVerifiedContactType(string) (bool, error)
	GetContact(string) (string, error)
	GetTemplateLang(context.Context) (string, error)
}

type SReceiverBase struct {
}

func (s SReceiverBase) IsEnabled() bool {
	return true
}

func (s SReceiverBase) GetDomainId() string {
	return ""
}

func (s SReceiverBase) IsEnabledContactType(_ string) (bool, error) {
	return true, nil
}

func (s SReceiverBase) IsVerifiedContactType(_ string) (bool, error) {
	return true, nil
}

func (s SReceiverBase) GetTemplateLang(ctx context.Context) (string, error) {
	return "", nil
}

type SContact struct {
	SReceiverBase
	contact string
}

func (s *SContact) GetContact(_ string) (string, error) {
	return s.contact, nil
}

func (s *SContact) IsRobot() bool {
	return false
}

func (s *SContact) IsReceiver() bool {
	return false
}
