package catalog

import (
	"context"
	"errors"
	"fmt"
	"slices"

	"github.com/gabehf/koito/internal/cfg"
	"github.com/gabehf/koito/internal/db"
	"github.com/gabehf/koito/internal/images"
	"github.com/gabehf/koito/internal/logger"
	"github.com/gabehf/koito/internal/mbz"
	"github.com/gabehf/koito/internal/models"
	"github.com/gabehf/koito/internal/utils"
	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
)

type AssociateAlbumOpts struct {
	Artists           []*models.Artist
	ReleaseMbzID      uuid.UUID
	ReleaseGroupMbzID uuid.UUID
	ReleaseName       string
	TrackName         string // required
	Mbzc              mbz.MusicBrainzCaller
	SkipCacheImage    bool
}

func AssociateAlbum(ctx context.Context, d db.DB, opts AssociateAlbumOpts) (*models.Album, error) {
	l := logger.FromContext(ctx)
	if opts.TrackName == "" {
		return nil, errors.New("AssociateAlbum: required parameter TrackName missing")
	}
	releaseTitle := opts.ReleaseName
	if releaseTitle == "" {
		releaseTitle = opts.TrackName
	}
	if opts.ReleaseMbzID != uuid.Nil {
		l.Debug().Msgf("Associating album '%s' by MusicBrainz release ID", releaseTitle)
		return matchAlbumByMbzReleaseID(ctx, d, opts)
	} else {
		l.Debug().Msgf("Associating album '%s' by title and artist", releaseTitle)
		return matchAlbumByTitle(ctx, d, opts)
	}
}

func matchAlbumByMbzReleaseID(ctx context.Context, d db.DB, opts AssociateAlbumOpts) (*models.Album, error) {
	l := logger.FromContext(ctx)
	a, err := d.GetAlbum(ctx, db.GetAlbumOpts{MusicBrainzID: opts.ReleaseMbzID})
	if err == nil {
		l.Debug().Msgf("Found release '%s' by MusicBrainz Release ID", a.Title)
		return &models.Album{
			ID:             a.ID,
			MbzID:          &opts.ReleaseMbzID,
			Title:          a.Title,
			VariousArtists: a.VariousArtists,
			Image:          a.Image,
		}, nil
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return nil, fmt.Errorf("matchAlbumByMbzReleaseID: %w", err)
	} else {
		l.Debug().Msgf("Album '%s' could not be found by MusicBrainz Release ID", opts.ReleaseName)
		rg, err := createOrUpdateAlbumWithMbzReleaseID(ctx, d, opts)
		if err != nil {
			return matchAlbumByTitle(ctx, d, opts)
		}
		return rg, nil
	}
}

func createOrUpdateAlbumWithMbzReleaseID(ctx context.Context, d db.DB, opts AssociateAlbumOpts) (*models.Album, error) {
	l := logger.FromContext(ctx)

	release, err := opts.Mbzc.GetRelease(ctx, opts.ReleaseMbzID)
	if err != nil {
		l.Warn().Msg("createOrUpdateAlbumWithMbzReleaseID: MusicBrainz unreachable, falling back to release title matching")
		return matchAlbumByTitle(ctx, d, opts)
	}

	var album *models.Album
	titles := []string{release.Title, opts.ReleaseName}
	utils.Unique(&titles)

	l.Debug().Msgf("Searching for albums '%v' from artist id %d and no associated MusicBrainz ID in DB", titles, opts.Artists[0].ID)
	album, err = d.GetAlbumWithNoMbzIDByTitles(ctx, opts.Artists[0].ID, titles)
	if err == nil {
		l.Debug().Msgf("Found album %s, updating with MusicBrainz Release ID...", album.Title)
		err := d.UpdateAlbum(ctx, db.UpdateAlbumOpts{
			ID:            album.ID,
			MusicBrainzID: opts.ReleaseMbzID,
		})
		if err != nil {
			l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to update album with MusicBrainz Release ID")
			return nil, fmt.Errorf("createOrUpdateAlbumWithMbzReleaseID: %w", err)
		}
		l.Debug().Msgf("Updated album '%s' with MusicBrainz Release ID", album.Title)

		if opts.ReleaseGroupMbzID != uuid.Nil {
			aliases, err := opts.Mbzc.GetReleaseTitles(ctx, opts.ReleaseGroupMbzID)
			if err == nil {
				l.Debug().Msgf("Associating aliases '%s' with Release '%s'", aliases, album.Title)
				err = d.SaveAlbumAliases(ctx, album.ID, aliases, "MusicBrainz")
				if err != nil {
					l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to save aliases")
				}
			} else {
				l.Info().AnErr("err", err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to get release group from MusicBrainz")
			}
		}
	} else if !errors.Is(err, pgx.ErrNoRows) {
		l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: error while searching for album by MusicBrainz Release ID")
		return nil, fmt.Errorf("createOrUpdateAlbumWithMbzReleaseID: %w", err)
	} else {
		l.Debug().Msgf("Album %s could not be found. Creating...", release.Title)

		var variousArtists bool
		for _, artistCredit := range release.ArtistCredit {
			if artistCredit.Name == "Various Artists" {
				l.Debug().Msgf("MusicBrainz release group '%s' detected as being a Various Artists compilation release", release.Title)
				variousArtists = true
			}
		}

		l.Debug().Msg("Searching for album images...")
		var imgid uuid.UUID
		imgUrl, err := images.GetAlbumImage(ctx, images.AlbumImageOpts{
			Artists:      utils.UniqueIgnoringCase(slices.Concat(utils.FlattenMbzArtistCreditNames(release.ArtistCredit), utils.FlattenArtistNames(opts.Artists))),
			Album:        release.Title,
			ReleaseMbzID: &opts.ReleaseMbzID,
		})

		if err == nil && imgUrl != "" {
			imgid = uuid.New()
			if !opts.SkipCacheImage {
				var size ImageSize
				if cfg.FullImageCacheEnabled() {
					size = ImageSizeFull
				} else {
					size = ImageSizeLarge
				}
				l.Debug().Msg("Downloading album image from source...")
				err = DownloadAndCacheImage(ctx, imgid, imgUrl, size)
				if err != nil {
					l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to cache image")
				}
			}
		}

		if err != nil {
			l.Debug().Msgf("createOrUpdateAlbumWithMbzReleaseID: failed to get album images for %s: %s", release.Title, err.Error())
		}

		album, err = d.SaveAlbum(ctx, db.SaveAlbumOpts{
			Title:          release.Title,
			MusicBrainzID:  opts.ReleaseMbzID,
			ArtistIDs:      utils.FlattenArtistIDs(opts.Artists),
			VariousArtists: variousArtists,
			Image:          imgid,
			ImageSrc:       imgUrl,
		})
		if err != nil {
			return nil, fmt.Errorf("createOrUpdateAlbumWithMbzReleaseID: %w", err)
		}

		if opts.ReleaseGroupMbzID != uuid.Nil {
			aliases, err := opts.Mbzc.GetReleaseTitles(ctx, opts.ReleaseGroupMbzID)
			if err == nil {
				l.Debug().Msgf("Associating aliases '%s' with Release '%s'", aliases, album.Title)
				err = d.SaveAlbumAliases(ctx, album.ID, aliases, "MusicBrainz")
				if err != nil {
					l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to save aliases")
				}
			} else {
				l.Info().AnErr("err", err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to get release group from MusicBrainz")
			}
		}

		l.Info().Msgf("Created album '%s' with MusicBrainz Release ID", album.Title)
	}

	return &models.Album{
		ID:             album.ID,
		MbzID:          &opts.ReleaseMbzID,
		Title:          album.Title,
		VariousArtists: album.VariousArtists,
	}, nil
}

func matchAlbumByTitle(ctx context.Context, d db.DB, opts AssociateAlbumOpts) (*models.Album, error) {
	l := logger.FromContext(ctx)

	var releaseName string
	if opts.ReleaseName != "" {
		releaseName = opts.ReleaseName
	} else {
		releaseName = opts.TrackName
	}

	a, err := d.GetAlbum(ctx, db.GetAlbumOpts{
		Title:    releaseName,
		ArtistID: opts.Artists[0].ID,
	})
	if err == nil {
		l.Debug().Msgf("Found album '%s' by artist and title", a.Title)
		if a.MbzID == nil && opts.ReleaseMbzID != uuid.Nil {
			l.Debug().Msgf("Updating album with id %d with MusicBrainz ID %s", a.ID, opts.ReleaseMbzID)
			err = d.UpdateAlbum(ctx, db.UpdateAlbumOpts{
				ID:            a.ID,
				MusicBrainzID: opts.ReleaseMbzID,
			})
			if err != nil {
				l.Err(err).Msg("matchAlbumByTitle: failed to associate existing release with MusicBrainz ID")
			}
		}
	} else if !errors.Is(err, pgx.ErrNoRows) {
		return nil, fmt.Errorf("matchAlbumByTitle: %w", err)
	} else {
		var imgid uuid.UUID
		imgUrl, err := images.GetAlbumImage(ctx, images.AlbumImageOpts{
			Artists:      utils.FlattenArtistNames(opts.Artists),
			Album:        opts.ReleaseName,
			ReleaseMbzID: &opts.ReleaseMbzID,
		})
		if err == nil && imgUrl != "" {
			imgid = uuid.New()
			if !opts.SkipCacheImage {
				var size ImageSize
				if cfg.FullImageCacheEnabled() {
					size = ImageSizeFull
				} else {
					size = ImageSizeLarge
				}
				l.Debug().Msg("Downloading album image from source...")
				err = DownloadAndCacheImage(ctx, imgid, imgUrl, size)
				if err != nil {
					l.Err(err).Msg("createOrUpdateAlbumWithMbzReleaseID: failed to cache image")
				}
			}
		}
		if err != nil {
			l.Debug().AnErr("error", err).Msgf("matchAlbumByTitle: failed to get album images for %s", opts.ReleaseName)
		}

		a, err = d.SaveAlbum(ctx, db.SaveAlbumOpts{
			Title:         releaseName,
			ArtistIDs:     utils.FlattenArtistIDs(opts.Artists),
			Image:         imgid,
			MusicBrainzID: opts.ReleaseMbzID,
			ImageSrc:      imgUrl,
		})
		if err != nil {
			return nil, fmt.Errorf("matchAlbumByTitle: %w", err)
		}
		l.Info().Msgf("Created album '%s' with artist and title", a.Title)
	}

	return &models.Album{
		ID:    a.ID,
		Title: a.Title,
	}, nil
}
