package service

import (
	"context"
	"encoding/json"
	"time"

	"github.com/RafayLabs/rcloud-base/internal/dao"
	"github.com/RafayLabs/rcloud-base/internal/models"
	v3 "github.com/RafayLabs/rcloud-base/proto/types/commonpb/v3"
	systemv3 "github.com/RafayLabs/rcloud-base/proto/types/systempb/v3"
	"github.com/google/uuid"
	bun "github.com/uptrace/bun"
	"google.golang.org/protobuf/types/known/timestamppb"
)

// PartnerService is the interface for partner operations
type PartnerService interface {
	// create partner
	Create(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error)
	// get partner by id
	GetByID(ctx context.Context, partnerId string) (*systemv3.Partner, error)
	// get partner by id
	GetByName(ctx context.Context, name string) (*systemv3.Partner, error)
	// create or update partner
	Update(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error)
	// delete partner
	Delete(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error)
	// list partner
	GetOnlyPartner(ctx context.Context) (*systemv3.Partner, error)
}

// partnerService implements PartnerService
type partnerService struct {
	db *bun.DB
}

// NewPartnerService return new partner service
func NewPartnerService(db *bun.DB) PartnerService {
	return &partnerService{db}
}

func (s *partnerService) Create(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error) {

	var sb []byte
	if partner.GetSpec().GetSettings() != nil {
		sb = json.RawMessage(partner.GetSpec().GetSettings().String())
	}
	//convert v3 spec to internal models
	part := models.Partner{
		Name:                      partner.GetMetadata().GetName(),
		Description:               partner.GetMetadata().GetDescription(),
		Trash:                     false,
		Settings:                  sb,
		Host:                      partner.GetSpec().GetHost(),
		Domain:                    partner.GetSpec().GetDomain(),
		TosLink:                   partner.GetSpec().GetTosLink(),
		LogoLink:                  partner.GetSpec().GetLogoLink(),
		NotificationEmail:         partner.GetSpec().GetNotificationEmail(),
		HelpdeskEmail:             partner.GetSpec().GetHelpdeskEmail(),
		ProductName:               partner.GetSpec().GetProductName(),
		SupportTeamName:           partner.GetSpec().GetSupportTeamName(),
		OpsHost:                   partner.GetSpec().GetOpsHost(),
		FavIconLink:               partner.GetSpec().GetFavIconLink(),
		IsTOTPEnabled:             partner.GetSpec().GetIsTOTPEnabled(),
		IsSyntheticPartnerEnabled: false,
		CreatedAt:                 time.Now(),
		ModifiedAt:                time.Now(),
	}
	entity, err := dao.Create(ctx, s.db, &part)
	if err != nil {
		return &systemv3.Partner{}, err
	}

	if createdPartner, ok := entity.(*models.Partner); ok {
		//update v3 spec
		partner.Metadata.Id = createdPartner.ID.String()
		partner.Metadata.ModifiedAt = timestamppb.New(createdPartner.ModifiedAt)
	}

	return partner, nil

}

func (s *partnerService) GetByID(ctx context.Context, id string) (*systemv3.Partner, error) {

	partner := &systemv3.Partner{
		ApiVersion: apiVersion,
		Kind:       partnerKind,
		Metadata: &v3.Metadata{
			Id: id,
		},
	}

	uid, err := uuid.Parse(id)
	if err != nil {
		return &systemv3.Partner{}, err
	}
	entity, err := dao.GetByID(ctx, s.db, uid, &models.Partner{})
	if err != nil {
		return &systemv3.Partner{}, err
	}

	if part, ok := entity.(*models.Partner); ok {

		partner.Metadata = &v3.Metadata{
			Name:        part.Name,
			Description: part.Description,
			ModifiedAt:  timestamppb.New(part.ModifiedAt),
		}
		partner.Spec = &systemv3.PartnerSpec{
			Host:              part.Host,
			Domain:            part.Domain,
			TosLink:           part.TosLink,
			LogoLink:          part.LogoLink,
			NotificationEmail: part.NotificationEmail,
			HelpdeskEmail:     part.HelpdeskEmail,
			ProductName:       part.ProductName,
			SupportTeamName:   part.SupportTeamName,
			OpsHost:           part.OpsHost,
			FavIconLink:       part.FavIconLink,
			IsTOTPEnabled:     part.IsTOTPEnabled,
			Settings:          nil, //TODO
		}

		return partner, nil

	} else {
		partner := &systemv3.Partner{
			ApiVersion: apiVersion,
			Kind:       partnerKind,
			Metadata: &v3.Metadata{
				Id: id,
			},
			Status: &v3.Status{
				ConditionStatus: v3.ConditionStatus_StatusNotSet,
				Reason:          "Unable to fetch partner information",
				LastUpdated:     timestamppb.Now(),
			},
		}

		return partner, nil
	}

}

func (s *partnerService) GetByName(ctx context.Context, name string) (*systemv3.Partner, error) {

	partner := &systemv3.Partner{
		ApiVersion: apiVersion,
		Kind:       partnerKind,
		Metadata: &v3.Metadata{
			Name: name,
		},
	}

	entity, err := dao.GetByName(ctx, s.db, name, &models.Partner{})
	if err != nil {
		return &systemv3.Partner{}, err
	}

	if part, ok := entity.(*models.Partner); ok {

		partner.Metadata = &v3.Metadata{
			Name:        part.Name,
			Id:          part.ID.String(),
			Description: part.Description,
			ModifiedAt:  timestamppb.New(part.ModifiedAt),
		}
		partner.Spec = &systemv3.PartnerSpec{
			Host:              part.Host,
			Domain:            part.Domain,
			TosLink:           part.TosLink,
			LogoLink:          part.LogoLink,
			NotificationEmail: part.NotificationEmail,
			HelpdeskEmail:     part.HelpdeskEmail,
			ProductName:       part.ProductName,
			SupportTeamName:   part.SupportTeamName,
			OpsHost:           part.OpsHost,
			FavIconLink:       part.FavIconLink,
			IsTOTPEnabled:     part.IsTOTPEnabled,
			Settings:          nil, //TODO
		}

		return partner, nil
	} else {
		partner := &systemv3.Partner{
			ApiVersion: apiVersion,
			Kind:       partnerKind,
			Metadata: &v3.Metadata{
				Name: name,
			},
			Status: &v3.Status{
				ConditionType:   "Describe",
				ConditionStatus: v3.ConditionStatus_StatusNotSet,
				Reason:          "Unable to fetch partner information",
				LastUpdated:     timestamppb.Now(),
			},
		}

		return partner, nil
	}

}

func (s *partnerService) Update(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error) {

	entity, err := dao.GetByName(ctx, s.db, partner.Metadata.Name, &models.Partner{})
	if err != nil {
		return &systemv3.Partner{}, err
	}

	var sb []byte
	if partner.GetSpec().GetSettings() != nil {
		sb = json.RawMessage(partner.GetSpec().GetSettings().String())
	}

	if part, ok := entity.(*models.Partner); ok {
		//update partner details
		part.Name = partner.GetMetadata().Name
		part.Description = partner.GetMetadata().GetDescription()
		part.Settings = sb
		part.Host = partner.GetSpec().GetHost()
		part.Domain = partner.GetSpec().GetDomain()
		part.TosLink = partner.GetSpec().GetTosLink()
		part.LogoLink = partner.GetSpec().GetLogoLink()
		part.NotificationEmail = partner.GetSpec().GetNotificationEmail()
		part.HelpdeskEmail = partner.GetSpec().GetHelpdeskEmail()
		part.ProductName = partner.GetSpec().GetProductName()
		part.SupportTeamName = partner.GetSpec().GetSupportTeamName()
		part.OpsHost = partner.GetSpec().GetOpsHost()
		part.FavIconLink = partner.GetSpec().GetFavIconLink()
		part.IsTOTPEnabled = partner.GetSpec().GetIsTOTPEnabled()
		part.ModifiedAt = time.Now()

		//Update the partner details
		_, err = dao.Update(ctx, s.db, part.ID, part)
		if err != nil {
			return &systemv3.Partner{}, err
		}

		//update metadata and status
		partner.Metadata.ModifiedAt = timestamppb.New(part.ModifiedAt)

	}

	return partner, nil
}

func (s *partnerService) Delete(ctx context.Context, partner *systemv3.Partner) (*systemv3.Partner, error) {
	entity, err := dao.GetByName(ctx, s.db, partner.Metadata.Name, &models.Partner{})
	if err != nil {
		return &systemv3.Partner{}, err
	}

	if part, ok := entity.(*models.Partner); ok {
		err := dao.Delete(ctx, s.db, part.ID, part)
		if err != nil {
			return &systemv3.Partner{}, err
		}
		return partner, nil
	}

	return partner, nil

}

func (s *partnerService) GetOnlyPartner(ctx context.Context) (partner *systemv3.Partner, err error) {
	var partners []models.Partner
	entities, err := dao.ListAll(ctx, s.db, &partners)
	if err != nil {
		return nil, err
	}
	if pts, ok := entities.(*[]models.Partner); ok {
		for _, part := range *pts {
			partner = &systemv3.Partner{
				Metadata: &v3.Metadata{
					Name:        part.Name,
					Id:          part.ID.String(),
					Description: part.Description,
					ModifiedAt:  timestamppb.New(part.ModifiedAt),
				},
				Spec: &systemv3.PartnerSpec{
					Host:              part.Host,
					Domain:            part.Domain,
					TosLink:           part.TosLink,
					LogoLink:          part.LogoLink,
					NotificationEmail: part.NotificationEmail,
					HelpdeskEmail:     part.HelpdeskEmail,
					ProductName:       part.ProductName,
					SupportTeamName:   part.SupportTeamName,
					OpsHost:           part.OpsHost,
					FavIconLink:       part.FavIconLink,
					IsTOTPEnabled:     part.IsTOTPEnabled,
					Settings:          nil,
				},
				Status: &v3.Status{
					ConditionType:   "Describe",
					ConditionStatus: v3.ConditionStatus_StatusOK,
					LastUpdated:     timestamppb.New(part.ModifiedAt),
				},
			}

			return partner, nil
		}
	}
	return partner, err
}
