package peers

//go:generate go run github.com/golang/mock/mockgen -package peers -destination=manager_mock.go -source=./manager.go -build_flags=-mod=mod

import (
	"context"
	"fmt"
	"time"

	log "github.com/sirupsen/logrus"

	"github.com/netbirdio/netbird/management/internals/controllers/network_map"
	"github.com/netbirdio/netbird/management/internals/modules/peers/ephemeral"
	"github.com/netbirdio/netbird/management/server/account"
	"github.com/netbirdio/netbird/management/server/activity"
	"github.com/netbirdio/netbird/management/server/integrations/integrated_validator"
	"github.com/netbirdio/netbird/management/server/peer"
	"github.com/netbirdio/netbird/management/server/permissions"
	"github.com/netbirdio/netbird/management/server/permissions/modules"
	"github.com/netbirdio/netbird/management/server/permissions/operations"
	"github.com/netbirdio/netbird/management/server/store"
	"github.com/netbirdio/netbird/shared/management/status"
)

type Manager interface {
	GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error)
	GetPeerAccountID(ctx context.Context, peerID string) (string, error)
	GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error)
	GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error)
	DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error
	SetNetworkMapController(networkMapController network_map.Controller)
	SetIntegratedPeerValidator(integratedPeerValidator integrated_validator.IntegratedValidator)
	SetAccountManager(accountManager account.Manager)
	GetPeerID(ctx context.Context, peerKey string) (string, error)
}

type managerImpl struct {
	store                   store.Store
	permissionsManager      permissions.Manager
	integratedPeerValidator integrated_validator.IntegratedValidator
	accountManager          account.Manager

	networkMapController network_map.Controller
}

func NewManager(store store.Store, permissionsManager permissions.Manager) Manager {
	return &managerImpl{
		store:              store,
		permissionsManager: permissionsManager,
	}
}

func (m *managerImpl) SetNetworkMapController(networkMapController network_map.Controller) {
	m.networkMapController = networkMapController
}

func (m *managerImpl) SetIntegratedPeerValidator(integratedPeerValidator integrated_validator.IntegratedValidator) {
	m.integratedPeerValidator = integratedPeerValidator
}

func (m *managerImpl) SetAccountManager(accountManager account.Manager) {
	m.accountManager = accountManager
}

func (m *managerImpl) GetPeer(ctx context.Context, accountID, userID, peerID string) (*peer.Peer, error) {
	allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
	if err != nil {
		return nil, fmt.Errorf("failed to validate user permissions: %w", err)
	}

	if !allowed {
		return nil, status.NewPermissionDeniedError()
	}

	return m.store.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
}

func (m *managerImpl) GetAllPeers(ctx context.Context, accountID, userID string) ([]*peer.Peer, error) {
	allowed, err := m.permissionsManager.ValidateUserPermissions(ctx, accountID, userID, modules.Peers, operations.Read)
	if err != nil {
		return nil, fmt.Errorf("failed to validate user permissions: %w", err)
	}

	if !allowed {
		return m.store.GetUserPeers(ctx, store.LockingStrengthNone, accountID, userID)
	}

	return m.store.GetAccountPeers(ctx, store.LockingStrengthNone, accountID, "", "")
}

func (m *managerImpl) GetPeerAccountID(ctx context.Context, peerID string) (string, error) {
	return m.store.GetAccountIDByPeerID(ctx, store.LockingStrengthNone, peerID)
}

func (m *managerImpl) GetPeersByGroupIDs(ctx context.Context, accountID string, groupsIDs []string) ([]*peer.Peer, error) {
	return m.store.GetPeersByGroupIDs(ctx, accountID, groupsIDs)
}

func (m *managerImpl) DeletePeers(ctx context.Context, accountID string, peerIDs []string, userID string, checkConnected bool) error {
	settings, err := m.store.GetAccountSettings(ctx, store.LockingStrengthNone, accountID)
	if err != nil {
		return err
	}
	dnsDomain := m.networkMapController.GetDNSDomain(settings)

	for _, peerID := range peerIDs {
		var eventsToStore []func()
		err = m.store.ExecuteInTransaction(ctx, func(transaction store.Store) error {
			peer, err := transaction.GetPeerByID(ctx, store.LockingStrengthNone, accountID, peerID)
			if err != nil {
				return err
			}

			if checkConnected && (peer.Status.Connected || peer.Status.LastSeen.After(time.Now().Add(-(ephemeral.EphemeralLifeTime - 10*time.Second)))) {
				return nil
			}

			if err := transaction.RemovePeerFromAllGroups(ctx, peerID); err != nil {
				return fmt.Errorf("failed to remove peer %s from groups", peerID)
			}

			peerPolicyRules, err := transaction.GetPolicyRulesByResourceID(ctx, store.LockingStrengthNone, accountID, peerID)
			if err != nil {
				return err
			}
			for _, rule := range peerPolicyRules {
				policy, err := transaction.GetPolicyByID(ctx, store.LockingStrengthNone, accountID, rule.PolicyID)
				if err != nil {
					return err
				}

				err = transaction.DeletePolicy(ctx, accountID, rule.PolicyID)
				if err != nil {
					return err
				}

				eventsToStore = append(eventsToStore, func() {
					m.accountManager.StoreEvent(ctx, userID, peer.ID, accountID, activity.PolicyRemoved, policy.EventMeta())
				})
			}

			if err = transaction.DeletePeer(ctx, accountID, peerID); err != nil {
				return err
			}

			eventsToStore = append(eventsToStore, func() {
				m.accountManager.StoreEvent(ctx, userID, peer.ID, accountID, activity.PeerRemovedByUser, peer.EventMeta(dnsDomain))
			})

			return nil
		})
		if err != nil {
			return err
		}

		if m.integratedPeerValidator != nil {
			if err = m.integratedPeerValidator.PeerDeleted(ctx, accountID, peerID, settings.Extra); err != nil {
				log.WithContext(ctx).Errorf("failed to delete peer %s from integrated validator: %v", peerID, err)
			}
		}

		for _, event := range eventsToStore {
			event()
		}
	}

	m.accountManager.UpdateAccountPeers(ctx, accountID)

	return nil
}

func (m *managerImpl) GetPeerID(ctx context.Context, peerKey string) (string, error) {
	return m.store.GetPeerIDByKey(ctx, store.LockingStrengthNone, peerKey)
}
