package endpoint

import (
	portainer "github.com/portainer/portainer/api"
	"github.com/portainer/portainer/api/dataservices"
	"github.com/portainer/portainer/api/internal/edge/cache"

	"github.com/rs/zerolog/log"
)

type ServiceTx struct {
	service *Service
	tx      portainer.Transaction
}

func (service ServiceTx) BucketName() string {
	return BucketName
}

// Endpoint returns an environment(endpoint) by ID.
func (service ServiceTx) Endpoint(ID portainer.EndpointID) (*portainer.Endpoint, error) {
	var endpoint portainer.Endpoint

	identifier := service.service.connection.ConvertToKey(int(ID))

	if err := service.tx.GetObject(BucketName, identifier, &endpoint); err != nil {
		return nil, err
	}

	endpoint.LastCheckInDate, _ = service.service.Heartbeat(ID)

	return &endpoint, nil
}

// UpdateEndpoint updates an environment(endpoint).
func (service ServiceTx) UpdateEndpoint(ID portainer.EndpointID, endpoint *portainer.Endpoint) error {
	identifier := service.service.connection.ConvertToKey(int(ID))

	if err := service.tx.UpdateObject(BucketName, identifier, endpoint); err != nil {
		return err
	}

	service.service.mu.Lock()
	if len(endpoint.EdgeID) > 0 {
		service.service.idxEdgeID[endpoint.EdgeID] = ID
	}

	service.service.heartbeats.Store(ID, endpoint.LastCheckInDate)
	service.service.mu.Unlock()

	cache.Del(endpoint.ID)

	return nil
}

// DeleteEndpoint deletes an environment(endpoint).
func (service ServiceTx) DeleteEndpoint(ID portainer.EndpointID) error {
	identifier := service.service.connection.ConvertToKey(int(ID))

	if err := service.tx.DeleteObject(BucketName, identifier); err != nil {
		return err
	}

	service.service.mu.Lock()
	for edgeID, endpointID := range service.service.idxEdgeID {
		if endpointID == ID {
			delete(service.service.idxEdgeID, edgeID)

			break
		}
	}

	service.service.heartbeats.Delete(ID)
	service.service.mu.Unlock()

	cache.Del(ID)

	return nil
}

// Endpoints return an array containing all the environments(endpoints).
func (service ServiceTx) Endpoints() ([]portainer.Endpoint, error) {
	var endpoints = make([]portainer.Endpoint, 0)

	return endpoints, service.tx.GetAll(
		BucketName,
		&portainer.Endpoint{},
		dataservices.AppendFn(&endpoints),
	)
}

// ReadAll retrieves all the elements that satisfy all the provided predicates.
func (service ServiceTx) ReadAll(predicates ...func(endpoint portainer.Endpoint) bool) ([]portainer.Endpoint, error) {
	return dataservices.BaseDataServiceTx[portainer.Endpoint, portainer.EndpointID]{Bucket: BucketName, Connection: service.service.connection, Tx: service.tx}.ReadAll(predicates...)
}

func (service ServiceTx) EndpointIDByEdgeID(edgeID string) (portainer.EndpointID, bool) {
	log.Error().Str("func", "EndpointIDByEdgeID").Msg("cannot be called inside a transaction")

	return 0, false
}

func (service ServiceTx) Heartbeat(endpointID portainer.EndpointID) (int64, bool) {
	log.Error().Str("func", "Heartbeat").Msg("cannot be called inside a transaction")

	return 0, false
}

func (service ServiceTx) UpdateHeartbeat(endpointID portainer.EndpointID) {
	log.Error().Str("func", "UpdateHeartbeat").Msg("cannot be called inside a transaction")
}

// CreateEndpoint assign an ID to a new environment(endpoint) and saves it.
func (service ServiceTx) Create(endpoint *portainer.Endpoint) error {
	if err := service.tx.CreateObjectWithId(BucketName, int(endpoint.ID), endpoint); err != nil {
		return err
	}

	service.service.mu.Lock()
	if len(endpoint.EdgeID) > 0 {
		service.service.idxEdgeID[endpoint.EdgeID] = endpoint.ID
	}

	service.service.heartbeats.Store(endpoint.ID, endpoint.LastCheckInDate)
	service.service.mu.Unlock()

	return nil
}

func (service ServiceTx) EndpointsByTeamID(teamID portainer.TeamID) ([]portainer.Endpoint, error) {
	var endpoints = make([]portainer.Endpoint, 0)

	return endpoints, service.tx.GetAll(
		BucketName,
		&portainer.Endpoint{},
		dataservices.FilterFn(&endpoints, func(e portainer.Endpoint) bool {
			for t := range e.TeamAccessPolicies {
				if t == teamID {
					return true
				}
			}

			return false
		}),
	)
}

// GetNextIdentifier returns the next identifier for an environment(endpoint).
func (service ServiceTx) GetNextIdentifier() int {
	return service.tx.GetNextIdentifier(BucketName)
}
