/*
Copyright © 2023-2024 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 jspf

import (
	"os"
	"time"

	"go.uploadedlobster.com/scotty/internal/config"
	"go.uploadedlobster.com/scotty/internal/i18n"
	"go.uploadedlobster.com/scotty/internal/models"
	"go.uploadedlobster.com/scotty/pkg/jspf"
)

type JSPFBackend struct {
	filePath string
	playlist jspf.Playlist
	append   bool
}

func (b *JSPFBackend) Name() string { return "jspf" }

func (b *JSPFBackend) Options() []models.BackendOption {
	return []models.BackendOption{{
		Name:  "file-path",
		Label: i18n.Tr("File path"),
		Type:  models.String,
	}, {
		Name:    "append",
		Label:   i18n.Tr("Append to file"),
		Type:    models.Bool,
		Default: "true",
	}, {
		Name:  "title",
		Label: i18n.Tr("Playlist title"),
		Type:  models.String,
	}, {
		Name:  "username",
		Label: i18n.Tr("User name"),
		Type:  models.String,
	}, {
		Name:  "identifier",
		Label: i18n.Tr("Unique playlist identifier"),
		Type:  models.String,
	}}
}

func (b *JSPFBackend) FromConfig(config *config.ServiceConfig) models.Backend {
	b.filePath = config.GetString("file-path")
	b.append = config.GetBool("append", true)
	b.playlist = jspf.Playlist{
		Title:      config.GetString("title"),
		Creator:    config.GetString("username"),
		Identifier: config.GetString("identifier"),
		Tracks:     make([]jspf.Track, 0),
		Extension: map[string]any{
			jspf.MusicBrainzPlaylistExtensionId: jspf.MusicBrainzPlaylistExtension{
				LastModifiedAt: time.Now(),
				Public:         true,
			},
		},
	}
	return b
}

func (b *JSPFBackend) StartImport() error {
	return b.readJSPF()
}

func (b *JSPFBackend) FinishImport() error {
	return b.writeJSPF()
}

func (b *JSPFBackend) ImportListens(export models.ListensResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
	for _, listen := range export.Items {
		track := listenAsTrack(listen)
		b.playlist.Tracks = append(b.playlist.Tracks, track)
		importResult.ImportCount += 1
		importResult.UpdateTimestamp(listen.ListenedAt)
	}

	progress <- models.Progress{}.FromImportResult(importResult)
	return importResult, nil
}

func (b *JSPFBackend) ImportLoves(export models.LovesResult, importResult models.ImportResult, progress chan models.Progress) (models.ImportResult, error) {
	for _, love := range export.Items {
		track := loveAsTrack(love)
		b.playlist.Tracks = append(b.playlist.Tracks, track)
		importResult.ImportCount += 1
		importResult.UpdateTimestamp(love.Created)
	}

	progress <- models.Progress{}.FromImportResult(importResult)
	return importResult, nil
}

func listenAsTrack(l models.Listen) jspf.Track {
	l.FillAdditionalInfo()
	track := trackAsTrack(l.Track)
	extension := makeMusicBrainzExtension(l.Track)
	extension.AddedAt = l.ListenedAt
	extension.AddedBy = l.UserName
	track.Extension[jspf.MusicBrainzTrackExtensionId] = extension

	if l.RecordingMBID != "" {
		track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(l.RecordingMBID))
	}

	return track
}

func loveAsTrack(l models.Love) jspf.Track {
	l.FillAdditionalInfo()
	track := trackAsTrack(l.Track)
	extension := makeMusicBrainzExtension(l.Track)
	extension.AddedAt = l.Created
	extension.AddedBy = l.UserName
	track.Extension[jspf.MusicBrainzTrackExtensionId] = extension

	recordingMBID := l.Track.RecordingMBID
	if l.RecordingMBID != "" {
		recordingMBID = l.RecordingMBID
	}
	if recordingMBID != "" {
		track.Identifier = append(track.Identifier, "https://musicbrainz.org/recording/"+string(recordingMBID))
	}

	return track
}

func trackAsTrack(t models.Track) jspf.Track {
	track := jspf.Track{
		Title:     t.TrackName,
		Album:     t.ReleaseName,
		Creator:   t.ArtistName(),
		TrackNum:  t.TrackNumber,
		Extension: map[string]any{},
	}

	return track
}

func makeMusicBrainzExtension(t models.Track) jspf.MusicBrainzTrackExtension {
	extension := jspf.MusicBrainzTrackExtension{
		AdditionalMetadata: t.AdditionalInfo,
		ArtistIdentifiers:  make([]string, len(t.ArtistMBIDs)),
	}

	for i, mbid := range t.ArtistMBIDs {
		extension.ArtistIdentifiers[i] = "https://musicbrainz.org/artist/" + string(mbid)
	}

	if t.ReleaseMBID != "" {
		extension.ReleaseIdentifier = "https://musicbrainz.org/release/" + string(t.ReleaseMBID)
	}

	// The tracknumber tag would be redundant
	delete(extension.AdditionalMetadata, "tracknumber")

	return extension
}

func (b *JSPFBackend) readJSPF() error {
	if b.append {
		file, err := os.Open(b.filePath)
		if err != nil {
			return nil
		}

		defer file.Close()
		stat, err := file.Stat()
		if err != nil {
			return err
		}

		if stat.Size() == 0 {
			// Zero length file, treat as a new file
			return nil
		} else {
			playlist := jspf.JSPF{}
			err := playlist.Read(file)
			if err != nil {
				return err
			}
			b.playlist = playlist.Playlist
		}
	}

	return nil
}

func (b *JSPFBackend) writeJSPF() error {
	playlist := jspf.JSPF{
		Playlist: b.playlist,
	}

	file, err := os.Create(b.filePath)
	if err != nil {
		return err
	}

	defer file.Close()
	return playlist.Write(file)
}