// -*- Mode: Go; indent-tabs-mode: t -*-

/*
 * Copyright (C) 2016-2019 Canonical Ltd
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package snapstate

import (
	"context"
	"time"

	"github.com/snapcore/snapd/overlord/state"
	"github.com/snapcore/snapd/snap"
	"github.com/snapcore/snapd/store"
	userclient "github.com/snapcore/snapd/usersession/client"
)

type ManagerBackend managerBackend

func SetSnapManagerBackend(s *SnapManager, b ManagerBackend) {
	s.backend = b
}

func MockSnapReadInfo(mock func(name string, si *snap.SideInfo) (*snap.Info, error)) (restore func()) {
	old := snapReadInfo
	snapReadInfo = mock
	return func() { snapReadInfo = old }
}

func MockMountPollInterval(intv time.Duration) (restore func()) {
	old := mountPollInterval
	mountPollInterval = intv
	return func() { mountPollInterval = old }
}

func MockRevisionDate(mock func(info *snap.Info) time.Time) (restore func()) {
	old := revisionDate
	if mock == nil {
		mock = revisionDateImpl
	}
	revisionDate = mock
	return func() { revisionDate = old }
}

func MockOpenSnapFile(mock func(path string, si *snap.SideInfo) (*snap.Info, snap.Container, error)) (restore func()) {
	prevOpenSnapFile := openSnapFile
	openSnapFile = mock
	return func() { openSnapFile = prevOpenSnapFile }
}

func MockErrtrackerReport(mock func(string, string, string, map[string]string) (string, error)) (restore func()) {
	prev := errtrackerReport
	errtrackerReport = mock
	return func() { errtrackerReport = prev }
}

func MockPrerequisitesRetryTimeout(d time.Duration) (restore func()) {
	old := prerequisitesRetryTimeout
	prerequisitesRetryTimeout = d
	return func() { prerequisitesRetryTimeout = old }
}

func MockOsutilEnsureUserGroup(mock func(name string, id uint32, extraUsers bool) error) (restore func()) {
	old := osutilEnsureUserGroup
	osutilEnsureUserGroup = mock
	return func() { osutilEnsureUserGroup = old }
}

var (
	CoreInfoInternal       = coreInfo
	CheckSnap              = checkSnap
	CanRemove              = canRemove
	CanDisable             = canDisable
	CachedStore            = cachedStore
	DefaultRefreshSchedule = defaultRefreshSchedule
	DoInstall              = doInstall
	UserFromUserID         = userFromUserID
	ValidateFeatureFlags   = validateFeatureFlags
	ResolveChannel         = resolveChannel

	CurrentSnaps = currentSnaps

	DefaultContentPlugProviders = defaultContentPlugProviders

	HasOtherInstances = hasOtherInstances

	SafetyMarginDiskSpace = safetyMarginDiskSpace
)

func PreviousSideInfo(snapst *SnapState) *snap.SideInfo {
	return snapst.previousSideInfo()
}

// helpers
var InstallSize = installSize

// aliases v2
var (
	ApplyAliasesChange    = applyAliasesChange
	AutoAliasesDelta      = autoAliasesDelta
	RefreshAliases        = refreshAliases
	CheckAliasesConflicts = checkAliasesConflicts
	DisableAliases        = disableAliases
	SwitchSummary         = switchSummary
)

// dbus
var (
	CheckDBusServiceConflicts = checkDBusServiceConflicts
)

// readme files
var (
	WriteSnapReadme = writeSnapReadme
	SnapReadme      = snapReadme
)

// refreshes
var (
	NewAutoRefresh                = newAutoRefresh
	NewRefreshHints               = newRefreshHints
	CanRefreshOnMeteredConnection = canRefreshOnMeteredConnection

	NewCatalogRefresh            = newCatalogRefresh
	CatalogRefreshDelayBase      = catalogRefreshDelayBase
	CatalogRefreshDelayWithDelta = catalogRefreshDelayWithDelta

	SoftCheckNothingRunningForRefresh     = softCheckNothingRunningForRefresh
	HardEnsureNothingRunningDuringRefresh = hardEnsureNothingRunningDuringRefresh
)

func MockNextRefresh(ar *autoRefresh, when time.Time) {
	ar.nextRefresh = when
}

func MockLastRefreshSchedule(ar *autoRefresh, schedule string) {
	ar.lastRefreshSchedule = schedule
}

func MockCatalogRefreshNextRefresh(cr *catalogRefresh, when time.Time) {
	cr.nextCatalogRefresh = when
}

func NextCatalogRefresh(cr *catalogRefresh) time.Time {
	return cr.nextCatalogRefresh
}

func MockRefreshRetryDelay(d time.Duration) func() {
	origRefreshRetryDelay := refreshRetryDelay
	refreshRetryDelay = d
	return func() {
		refreshRetryDelay = origRefreshRetryDelay
	}
}

func MockIsOnMeteredConnection(mock func() (bool, error)) func() {
	old := IsOnMeteredConnection
	IsOnMeteredConnection = mock
	return func() {
		IsOnMeteredConnection = old
	}
}

func MockLocalInstallCleanupWait(d time.Duration) (restore func()) {
	old := localInstallCleanupWait
	localInstallCleanupWait = d
	return func() {
		localInstallCleanupWait = old
	}
}

func MockLocalInstallLastCleanup(t time.Time) (restore func()) {
	old := localInstallLastCleanup
	localInstallLastCleanup = t
	return func() {
		localInstallLastCleanup = old
	}
}

func MockAsyncPendingRefreshNotification(fn func(context.Context, *userclient.Client, *userclient.PendingSnapRefreshInfo)) (restore func()) {
	old := asyncPendingRefreshNotification
	asyncPendingRefreshNotification = fn
	return func() {
		asyncPendingRefreshNotification = old
	}
}

// re-refresh related
var (
	RefreshedSnaps  = refreshedSnaps
	ReRefreshFilter = reRefreshFilter
)

type UpdateFilter = updateFilter

func MockReRefreshUpdateMany(f func(context.Context, *state.State, []string, int, UpdateFilter, *Flags, string) ([]string, []*state.TaskSet, error)) (restore func()) {
	old := reRefreshUpdateMany
	reRefreshUpdateMany = f
	return func() {
		reRefreshUpdateMany = old
	}
}

func MockReRefreshRetryTimeout(d time.Duration) (restore func()) {
	old := reRefreshRetryTimeout
	reRefreshRetryTimeout = d
	return func() {
		reRefreshRetryTimeout = old
	}
}

// aux store info
var (
	AuxStoreInfoFilename = auxStoreInfoFilename
	RetrieveAuxStoreInfo = retrieveAuxStoreInfo
	KeepAuxStoreInfo     = keepAuxStoreInfo
	DiscardAuxStoreInfo  = discardAuxStoreInfo
)

type AuxStoreInfo = auxStoreInfo

// link, misc handlers
var (
	MissingDisabledServices = missingDisabledServices
)

func (m *SnapManager) MaybeUndoRemodelBootChanges(t *state.Task) error {
	return m.maybeUndoRemodelBootChanges(t)
}

func MockPidsOfSnap(f func(instanceName string) (map[string][]int, error)) func() {
	old := pidsOfSnap
	pidsOfSnap = f
	return func() {
		pidsOfSnap = old
	}
}

func MockCurrentSnaps(f func(st *state.State) ([]*store.CurrentSnap, error)) func() {
	old := currentSnaps
	currentSnaps = f
	return func() {
		currentSnaps = old
	}
}

func MockInstallSize(f func(st *state.State, snaps []*snap.Info, userID int) (uint64, error)) func() {
	old := installSize
	installSize = f
	return func() {
		installSize = old
	}
}

func MockGenerateSnapdWrappers(f func(snapInfo *snap.Info) error) func() {
	old := generateSnapdWrappers
	generateSnapdWrappers = f
	return func() {
		generateSnapdWrappers = old
	}
}

var (
	NotifyLinkParticipants = notifyLinkParticipants
)

// autorefresh
var (
	InhibitRefresh = inhibitRefresh
	MaxInhibition  = maxInhibition
)

func NewBusySnapError(info *snap.Info, pids []int, busyAppNames, busyHookNames []string) *BusySnapError {
	return &BusySnapError{
		SnapInfo:      info,
		pids:          pids,
		busyAppNames:  busyAppNames,
		busyHookNames: busyHookNames,
	}
}

func MockGenericRefreshCheck(fn func(info *snap.Info, canAppRunDuringRefresh func(app *snap.AppInfo) bool) error) (restore func()) {
	old := genericRefreshCheck
	genericRefreshCheck = fn
	return func() { genericRefreshCheck = old }
}

func (m *autoRefresh) EnsureRefreshHoldAtLeast(d time.Duration) error {
	return m.ensureRefreshHoldAtLeast(d)
}
