package debrid

import (
	"context"
	"errors"
	"fmt"
	"github.com/sirrobot01/decypharr/internal/config"
	"github.com/sirrobot01/decypharr/internal/logger"
	"github.com/sirrobot01/decypharr/internal/utils"
	"github.com/sirrobot01/decypharr/pkg/arr"
	"github.com/sirrobot01/decypharr/pkg/debrid/providers/alldebrid"
	"github.com/sirrobot01/decypharr/pkg/debrid/providers/debrid_link"
	"github.com/sirrobot01/decypharr/pkg/debrid/providers/realdebrid"
	"github.com/sirrobot01/decypharr/pkg/debrid/providers/torbox"
	"github.com/sirrobot01/decypharr/pkg/debrid/store"
	"github.com/sirrobot01/decypharr/pkg/debrid/types"
	"sync"
)

type Debrid struct {
	cache  *store.Cache // Could be nil if not using WebDAV
	client types.Client // HTTP client for making requests to the debrid service
}

func (de *Debrid) Client() types.Client {
	return de.client
}

func (de *Debrid) Cache() *store.Cache {
	return de.cache
}

type Storage struct {
	debrids  map[string]*Debrid
	mu       sync.RWMutex
	lastUsed string
}

func NewStorage() *Storage {
	cfg := config.Get()

	_logger := logger.Default()

	debrids := make(map[string]*Debrid)

	for _, dc := range cfg.Debrids {
		client, err := createDebridClient(dc)
		if err != nil {
			_logger.Error().Err(err).Str("Debrid", dc.Name).Msg("failed to connect to debrid client")
			continue
		}
		var cache *store.Cache
		_log := client.Logger()
		if dc.UseWebDav {
			cache = store.NewDebridCache(dc, client)
			_log.Info().Msg("Debrid Service started with WebDAV")
		} else {
			_log.Info().Msg("Debrid Service started")
		}
		debrids[dc.Name] = &Debrid{
			cache:  cache,
			client: client,
		}
	}

	d := &Storage{
		debrids:  debrids,
		lastUsed: "",
	}
	return d
}

func (d *Storage) Debrid(name string) *Debrid {
	d.mu.RLock()
	defer d.mu.RUnlock()
	if debrid, exists := d.debrids[name]; exists {
		return debrid
	}
	return nil
}

func (d *Storage) Debrids() map[string]*Debrid {
	d.mu.RLock()
	defer d.mu.RUnlock()
	debridsCopy := make(map[string]*Debrid)
	for name, debrid := range d.debrids {
		if debrid != nil {
			debridsCopy[name] = debrid
		}
	}
	return debridsCopy
}

func (d *Storage) Client(name string) types.Client {
	d.mu.RLock()
	defer d.mu.RUnlock()
	if client, exists := d.debrids[name]; exists {
		return client.client
	}
	return nil
}

func (d *Storage) Reset() {
	d.mu.Lock()
	d.debrids = make(map[string]*Debrid)
	d.mu.Unlock()
	d.lastUsed = ""
}

func (d *Storage) Clients() map[string]types.Client {
	d.mu.RLock()
	defer d.mu.RUnlock()
	clientsCopy := make(map[string]types.Client)
	for name, debrid := range d.debrids {
		if debrid != nil && debrid.client != nil {
			clientsCopy[name] = debrid.client
		}
	}
	return clientsCopy
}

func (d *Storage) Caches() map[string]*store.Cache {
	d.mu.RLock()
	defer d.mu.RUnlock()
	cachesCopy := make(map[string]*store.Cache)
	for name, debrid := range d.debrids {
		if debrid != nil && debrid.cache != nil {
			cachesCopy[name] = debrid.cache
		}
	}
	return cachesCopy
}

func (d *Storage) FilterClients(filter func(types.Client) bool) map[string]types.Client {
	d.mu.Lock()
	defer d.mu.Unlock()
	filteredClients := make(map[string]types.Client)
	for name, client := range d.debrids {
		if client != nil && filter(client.client) {
			filteredClients[name] = client.client
		}
	}
	return filteredClients
}

func createDebridClient(dc config.Debrid) (types.Client, error) {
	switch dc.Name {
	case "realdebrid":
		return realdebrid.New(dc)
	case "torbox":
		return torbox.New(dc)
	case "debridlink":
		return debrid_link.New(dc)
	case "alldebrid":
		return alldebrid.New(dc)
	default:
		return realdebrid.New(dc)
	}
}

func Process(ctx context.Context, store *Storage, selectedDebrid string, magnet *utils.Magnet, a *arr.Arr, action string, overrideDownloadUncached bool) (*types.Torrent, error) {

	debridTorrent := &types.Torrent{
		InfoHash: magnet.InfoHash,
		Magnet:   magnet,
		Name:     magnet.Name,
		Arr:      a,
		Size:     magnet.Size,
		Files:    make(map[string]types.File),
	}

	clients := store.FilterClients(func(c types.Client) bool {
		if selectedDebrid != "" && c.Name() != selectedDebrid {
			return false
		}
		return true
	})

	if len(clients) == 0 {
		return nil, fmt.Errorf("no debrid clients available")
	}

	errs := make([]error, 0, len(clients))

	// Override first, arr second, debrid third

	if overrideDownloadUncached {
		debridTorrent.DownloadUncached = true
	} else if a.DownloadUncached != nil {
		// Arr cached is set
		debridTorrent.DownloadUncached = *a.DownloadUncached
	} else {
		debridTorrent.DownloadUncached = false
	}

	for index, db := range clients {
		_logger := db.Logger()
		_logger.Info().
			Str("Debrid", db.Name()).
			Str("Arr", a.Name).
			Str("Hash", debridTorrent.InfoHash).
			Str("Name", debridTorrent.Name).
			Str("Action", action).
			Msg("Processing torrent")

		if !overrideDownloadUncached && a.DownloadUncached == nil {
			debridTorrent.DownloadUncached = db.GetDownloadUncached()
		}

		dbt, err := db.SubmitMagnet(debridTorrent)
		if err != nil || dbt == nil || dbt.Id == "" {
			errs = append(errs, err)
			continue
		}
		dbt.Arr = a
		_logger.Info().Str("id", dbt.Id).Msgf("Torrent: %s submitted to %s", dbt.Name, db.Name())
		store.lastUsed = index

		torrent, err := db.CheckStatus(dbt)
		if err != nil && torrent != nil && torrent.Id != "" {
			// Delete the torrent if it was not downloaded
			go func(id string) {
				_ = db.DeleteTorrent(id)
			}(torrent.Id)
		}
		if err != nil {
			errs = append(errs, err)
			continue
		}
		if torrent == nil {
			errs = append(errs, fmt.Errorf("torrent %s returned nil after checking status", dbt.Name))
			continue
		}
		return torrent, nil
	}
	if len(errs) == 0 {
		return nil, fmt.Errorf("failed to process torrent: no clients available")
	}
	joinedErrors := errors.Join(errs...)
	return nil, fmt.Errorf("failed to process torrent: %w", joinedErrors)
}
