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

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 deezer

import (
	"fmt"
	"math"
	"net/url"
	"sort"
	"time"

	"go.uploadedlobster.com/scotty/internal/auth"
	"go.uploadedlobster.com/scotty/internal/config"
	"go.uploadedlobster.com/scotty/internal/i18n"
	"go.uploadedlobster.com/scotty/internal/models"
	"golang.org/x/oauth2"
)

type DeezerApiBackend struct {
	client       Client
	clientId     string
	clientSecret string
}

func (b *DeezerApiBackend) Name() string { return "deezer" }

func (b *DeezerApiBackend) Options() []models.BackendOption {
	return []models.BackendOption{{
		Name:  "client-id",
		Label: i18n.Tr("Client ID"),
		Type:  models.String,
	}, {
		Name:  "client-secret",
		Label: i18n.Tr("Client secret"),
		Type:  models.Secret,
	}}
}

func (b *DeezerApiBackend) FromConfig(config *config.ServiceConfig) models.Backend {
	b.clientId = config.GetString("client-id")
	b.clientSecret = config.GetString("client-secret")
	return b
}

func (b *DeezerApiBackend) OAuth2Strategy(redirectUrl *url.URL) auth.OAuth2Strategy {
	conf := oauth2.Config{
		ClientID:     b.clientId,
		ClientSecret: b.clientSecret,
		Scopes: []string{
			"offline_access,basic_access,listening_history",
		},
		RedirectURL: redirectUrl.String(),
		Endpoint: oauth2.Endpoint{
			AuthURL:  "https://connect.deezer.com/oauth/auth.php",
			TokenURL: "https://connect.deezer.com/oauth/access_token.php",
		},
	}

	return deezerStrategy{conf: conf}
}

func (b *DeezerApiBackend) OAuth2Setup(token oauth2.TokenSource) error {
	b.client = NewClient(token)
	return nil
}

func (b *DeezerApiBackend) ExportListens(oldestTimestamp time.Time, results chan models.ListensResult, progress chan models.Progress) {
	// Choose a high offset, we attempt to search the loves backwards starting
	// at the oldest one.
	offset := math.MaxInt32
	perPage := MaxItemsPerGet

	startTime := time.Now()
	minTime := oldestTimestamp

	totalDuration := startTime.Sub(oldestTimestamp)

	defer close(results)

	p := models.Progress{Total: int64(totalDuration.Seconds())}

out:
	for {
		result, err := b.client.UserHistory(offset, perPage)
		if err != nil {
			progress <- p.Complete()
			results <- models.ListensResult{Error: err}
			return
		}

		// The offset was higher then the actual number of tracks. Adjust the offset
		// and continue.
		if offset >= result.Total {
			p.Total = int64(result.Total)
			offset = max(result.Total-perPage, 0)
			continue
		}

		count := len(result.Tracks)
		if count == 0 {
			break out
		}

		listens := make(models.ListensList, 0, perPage)
		for _, track := range result.Tracks {
			listen := track.AsListen()
			if listen.ListenedAt.Unix() > oldestTimestamp.Unix() {
				listens = append(listens, listen)
			} else {
				break
			}
		}

		sort.Sort(listens)
		if len(listens) > 0 {
			minTime = listens[0].ListenedAt
		}

		remainingTime := startTime.Sub(minTime)
		p.Elapsed = int64(totalDuration.Seconds() - remainingTime.Seconds())
		progress <- p
		results <- models.ListensResult{Items: listens, OldestTimestamp: minTime}

		if offset <= 0 {
			// This was the last request, no further results
			break out
		}

		offset -= perPage
		if offset < 0 {
			offset = 0
		}
	}

	results <- models.ListensResult{OldestTimestamp: minTime}
	progress <- p.Complete()
}

func (b *DeezerApiBackend) ExportLoves(oldestTimestamp time.Time, results chan models.LovesResult, progress chan models.Progress) {
	// Choose a high offset, we attempt to search the loves backwards starting
	// at the oldest one.
	offset := math.MaxInt32
	perPage := MaxItemsPerGet

	defer close(results)

	p := models.Progress{Total: int64(perPage)}
	var totalCount int

out:
	for {
		result, err := b.client.UserTracks(offset, perPage)
		if err != nil {
			progress <- p.Complete()
			results <- models.LovesResult{Error: err}
			return
		}

		// The offset was higher then the actual number of tracks. Adjust the offset
		// and continue.
		if offset >= result.Total {
			p.Total = int64(result.Total)
			totalCount = result.Total
			offset = max(result.Total-perPage, 0)
			continue
		}

		count := len(result.Tracks)
		if count == 0 {
			break out
		}

		loves := make(models.LovesList, 0, perPage)
		for _, track := range result.Tracks {
			love := track.AsLove()
			if love.Created.Unix() > oldestTimestamp.Unix() {
				loves = append(loves, love)
			} else {
				totalCount -= 1
				break
			}
		}

		sort.Sort(loves)
		results <- models.LovesResult{Items: loves, Total: totalCount}
		p.Elapsed += int64(count)
		progress <- p

		if offset <= 0 {
			// This was the last request, no further results
			break out
		}

		offset -= perPage
		if offset < 0 {
			offset = 0
		}
	}

	progress <- p.Complete()
}

func (t Listen) AsListen() models.Listen {
	love := models.Listen{
		ListenedAt: time.Unix(t.Timestamp, 0),
		Track:      t.Track.AsTrack(),
	}

	return love
}

func (t LovedTrack) AsLove() models.Love {
	love := models.Love{
		Created: time.Unix(t.AddedAt, 0),
		Track:   t.Track.AsTrack(),
	}

	return love
}

func (t Track) AsTrack() models.Track {
	track := models.Track{
		TrackName:      t.Title,
		ReleaseName:    t.Album.Title,
		ArtistNames:    []string{t.Artist.Name},
		Duration:       time.Duration(t.Duration * int(time.Second)),
		AdditionalInfo: map[string]any{},
	}

	info := track.AdditionalInfo
	info["music_service"] = "deezer.com"
	info["origin_url"] = t.Link
	info["deezer_id"] = t.Link
	info["deezer_album_id"] = fmt.Sprintf("https://www.deezer.com/album/%v", t.Album.Id)
	info["deezer_artist_id"] = fmt.Sprintf("https://www.deezer.com/artist/%v", t.Artist.Id)

	return track
}