/*
Copyright © 2023 Philipp Wolfer <phw@uploadedlobster.com>

This file is part of Scotty.

Scotty is free software: you can redistribute it and/or modify it under the
terms of the GNU General Public License as published by the Free Software
Foundation, either version 3 of the License, or (at your option) any later version.

Scotty 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
Scotty. If not, see <https://www.gnu.org/licenses/>.
*/
package backends

import (
	"errors"
	"fmt"
	"reflect"
	"strings"

	"github.com/spf13/viper"
	"go.uploadedlobster.com/scotty/internal/backends/deezer"
	"go.uploadedlobster.com/scotty/internal/backends/dump"
	"go.uploadedlobster.com/scotty/internal/backends/funkwhale"
	"go.uploadedlobster.com/scotty/internal/backends/jspf"
	"go.uploadedlobster.com/scotty/internal/backends/lastfm"
	"go.uploadedlobster.com/scotty/internal/backends/listenbrainz"
	"go.uploadedlobster.com/scotty/internal/backends/maloja"
	"go.uploadedlobster.com/scotty/internal/backends/scrobblerlog"
	"go.uploadedlobster.com/scotty/internal/backends/spotify"
	"go.uploadedlobster.com/scotty/internal/backends/subsonic"
	"go.uploadedlobster.com/scotty/internal/models"
)

type BackendInfo struct {
	Name               string
	ExportCapabilities []Capability
	ImportCapabilities []Capability
}

type Capability = string

func ResolveBackend[T interface{}](config *viper.Viper) (T, error) {
	backendName, backend, err := resolveBackend(config)
	var result T
	if err != nil {
		return result, err
	}
	implements, interfaceName := ImplementsInterface[T](&backend)
	if implements {
		result = backend.(T)
	} else {
		err = errors.New(
			fmt.Sprintf("Backend %s does not implement %s", backendName, interfaceName))
	}

	return result, err
}

func GetBackends() []BackendInfo {
	backends := make([]BackendInfo, 0)
	for name, backendFunc := range knownBackends {
		backend := backendFunc()
		info := BackendInfo{
			Name:               name,
			ExportCapabilities: getExportCapabilities(backend),
			ImportCapabilities: getImportCapabilities(backend),
		}
		backends = append(backends, info)
	}

	return backends
}

var knownBackends = map[string]func() models.Backend{
	"deezer":        func() models.Backend { return &deezer.DeezerApiBackend{} },
	"dump":          func() models.Backend { return &dump.DumpBackend{} },
	"funkwhale":     func() models.Backend { return &funkwhale.FunkwhaleApiBackend{} },
	"jspf":          func() models.Backend { return &jspf.JSPFBackend{} },
	"lastfm":        func() models.Backend { return &lastfm.LastfmApiBackend{} },
	"listenbrainz":  func() models.Backend { return &listenbrainz.ListenBrainzApiBackend{} },
	"maloja":        func() models.Backend { return &maloja.MalojaApiBackend{} },
	"scrobbler-log": func() models.Backend { return &scrobblerlog.ScrobblerLogBackend{} },
	"spotify":       func() models.Backend { return &spotify.SpotifyApiBackend{} },
	"subsonic":      func() models.Backend { return &subsonic.SubsonicApiBackend{} },
}

func resolveBackend(config *viper.Viper) (string, models.Backend, error) {
	backendName := config.GetString("backend")
	backendType := knownBackends[backendName]
	if backendType == nil {
		return backendName, nil, fmt.Errorf("Unknown backend %s", backendName)
	}
	return backendName, backendType().FromConfig(config), nil
}

func ImplementsInterface[T interface{}](backend *models.Backend) (bool, string) {
	expectedInterface := reflect.TypeOf((*T)(nil)).Elem()
	implements := backend != nil && reflect.TypeOf(*backend).Implements(expectedInterface)
	return implements, expectedInterface.Name()
}

func getExportCapabilities(backend models.Backend) []string {
	caps := make([]Capability, 0)
	var name string
	var found bool
	name, found = checkCapability[models.ListensExport](backend, "export")
	if found {
		caps = append(caps, name)
	}
	name, found = checkCapability[models.LovesExport](backend, "export")
	if found {
		caps = append(caps, name)
	}
	return caps
}

func getImportCapabilities(backend models.Backend) []Capability {
	caps := make([]Capability, 0)
	var name string
	var found bool
	name, found = checkCapability[models.ListensImport](backend, "import")
	if found {
		caps = append(caps, name)
	}
	name, found = checkCapability[models.LovesImport](backend, "import")
	if found {
		caps = append(caps, name)
	}
	return caps
}

func checkCapability[T interface{}](backend models.Backend, suffix string) (string, bool) {
	implements, name := ImplementsInterface[T](&backend)
	if implements {
		cap, found := strings.CutSuffix(strings.ToLower(name), suffix)
		if found {
			return cap, found
		}
	}

	return "", false
}