package updater

import (
	"context"
	"errors"
	"fmt"
	"net/http"

	"github.com/safing/portbase/log"
)

// Errors returned by the updater package.
var (
	ErrNotFound                  = errors.New("the requested file could not be found")
	ErrNotAvailableLocally       = errors.New("the requested file is not available locally")
	ErrVerificationNotConfigured = errors.New("verification not configured for this resource")
)

// GetFile returns the selected (mostly newest) file with the given
// identifier or an error, if it fails.
func (reg *ResourceRegistry) GetFile(identifier string) (*File, error) {
	reg.RLock()
	res, ok := reg.resources[identifier]
	reg.RUnlock()
	if !ok {
		return nil, ErrNotFound
	}

	file := res.GetFile()
	// check if file is available locally
	if file.version.Available {
		file.markActiveWithLocking()

		// Verify file, if configured.
		_, err := file.Verify()
		if err != nil && !errors.Is(err, ErrVerificationNotConfigured) {
			// TODO: If verification is required, try deleting the resource and downloading it again.
			return nil, fmt.Errorf("failed to verify file: %w", err)
		}

		return file, nil
	}

	// check if online
	if !reg.Online {
		return nil, ErrNotAvailableLocally
	}

	// check download dir
	err := reg.tmpDir.Ensure()
	if err != nil {
		return nil, fmt.Errorf("could not prepare tmp directory for download: %w", err)
	}

	// Start registry operation.
	reg.state.StartOperation(StateFetching)
	defer reg.state.EndOperation()

	// download file
	log.Tracef("%s: starting download of %s", reg.Name, file.versionedPath)
	client := &http.Client{}
	for tries := 0; tries < 5; tries++ {
		err = reg.fetchFile(context.TODO(), client, file.version, tries)
		if err != nil {
			log.Tracef("%s: failed to download %s: %s, retrying (%d)", reg.Name, file.versionedPath, err, tries+1)
		} else {
			file.markActiveWithLocking()

			// TODO: We just download the file - should we verify it again?
			return file, nil
		}
	}
	log.Warningf("%s: failed to download %s: %s", reg.Name, file.versionedPath, err)
	return nil, err
}

// GetVersion returns the selected version of the given identifier.
// The returned resource version may not be modified.
func (reg *ResourceRegistry) GetVersion(identifier string) (*ResourceVersion, error) {
	reg.RLock()
	res, ok := reg.resources[identifier]
	reg.RUnlock()
	if !ok {
		return nil, ErrNotFound
	}

	res.Lock()
	defer res.Unlock()

	return res.SelectedVersion, nil
}
