package api

import (
	"context"
	"fmt"
	"strconv"

	"github.com/stashapp/stash/internal/static"
	"github.com/stashapp/stash/pkg/models"
	"github.com/stashapp/stash/pkg/plugin/hook"
	"github.com/stashapp/stash/pkg/sliceutil/stringslice"
	"github.com/stashapp/stash/pkg/utils"
)

// used to refetch movie after hooks run
func (r *mutationResolver) getMovie(ctx context.Context, id int) (ret *models.Movie, err error) {
	if err := r.withTxn(ctx, func(ctx context.Context) error {
		ret, err = r.repository.Movie.Find(ctx, id)
		return err
	}); err != nil {
		return nil, err
	}

	return ret, nil
}

func (r *mutationResolver) MovieCreate(ctx context.Context, input MovieCreateInput) (*models.Movie, error) {
	translator := changesetTranslator{
		inputMap: getUpdateInputMap(ctx),
	}

	// Populate a new movie from the input
	newMovie := models.NewMovie()

	newMovie.Name = input.Name
	newMovie.Aliases = translator.string(input.Aliases)
	newMovie.Duration = input.Duration
	newMovie.Rating = input.Rating100
	newMovie.Director = translator.string(input.Director)
	newMovie.Synopsis = translator.string(input.Synopsis)
	newMovie.URL = translator.string(input.URL)

	var err error

	newMovie.Date, err = translator.datePtr(input.Date)
	if err != nil {
		return nil, fmt.Errorf("converting date: %w", err)
	}
	newMovie.StudioID, err = translator.intPtrFromString(input.StudioID)
	if err != nil {
		return nil, fmt.Errorf("converting studio id: %w", err)
	}

	// Process the base 64 encoded image string
	var frontimageData []byte
	if input.FrontImage != nil {
		frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
		if err != nil {
			return nil, fmt.Errorf("processing front image: %w", err)
		}
	}

	// Process the base 64 encoded image string
	var backimageData []byte
	if input.BackImage != nil {
		backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
		if err != nil {
			return nil, fmt.Errorf("processing back image: %w", err)
		}
	}

	// HACK: if back image is being set, set the front image to the default.
	// This is because we can't have a null front image with a non-null back image.
	if len(frontimageData) == 0 && len(backimageData) != 0 {
		frontimageData = static.ReadAll(static.DefaultMovieImage)
	}

	// Start the transaction and save the movie
	if err := r.withTxn(ctx, func(ctx context.Context) error {
		qb := r.repository.Movie

		err = qb.Create(ctx, &newMovie)
		if err != nil {
			return err
		}

		// update image table
		if len(frontimageData) > 0 {
			if err := qb.UpdateFrontImage(ctx, newMovie.ID, frontimageData); err != nil {
				return err
			}
		}

		if len(backimageData) > 0 {
			if err := qb.UpdateBackImage(ctx, newMovie.ID, backimageData); err != nil {
				return err
			}
		}

		return nil
	}); err != nil {
		return nil, err
	}

	r.hookExecutor.ExecutePostHooks(ctx, newMovie.ID, hook.MovieCreatePost, input, nil)
	return r.getMovie(ctx, newMovie.ID)
}

func (r *mutationResolver) MovieUpdate(ctx context.Context, input MovieUpdateInput) (*models.Movie, error) {
	movieID, err := strconv.Atoi(input.ID)
	if err != nil {
		return nil, fmt.Errorf("converting id: %w", err)
	}

	translator := changesetTranslator{
		inputMap: getUpdateInputMap(ctx),
	}

	// Populate movie from the input
	updatedMovie := models.NewMoviePartial()

	updatedMovie.Name = translator.optionalString(input.Name, "name")
	updatedMovie.Aliases = translator.optionalString(input.Aliases, "aliases")
	updatedMovie.Duration = translator.optionalInt(input.Duration, "duration")
	updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100")
	updatedMovie.Director = translator.optionalString(input.Director, "director")
	updatedMovie.Synopsis = translator.optionalString(input.Synopsis, "synopsis")
	updatedMovie.URL = translator.optionalString(input.URL, "url")

	updatedMovie.Date, err = translator.optionalDate(input.Date, "date")
	if err != nil {
		return nil, fmt.Errorf("converting date: %w", err)
	}
	updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
	if err != nil {
		return nil, fmt.Errorf("converting studio id: %w", err)
	}

	var frontimageData []byte
	frontImageIncluded := translator.hasField("front_image")
	if input.FrontImage != nil {
		frontimageData, err = utils.ProcessImageInput(ctx, *input.FrontImage)
		if err != nil {
			return nil, fmt.Errorf("processing front image: %w", err)
		}
	}

	var backimageData []byte
	backImageIncluded := translator.hasField("back_image")
	if input.BackImage != nil {
		backimageData, err = utils.ProcessImageInput(ctx, *input.BackImage)
		if err != nil {
			return nil, fmt.Errorf("processing back image: %w", err)
		}
	}

	// Start the transaction and save the movie
	var movie *models.Movie
	if err := r.withTxn(ctx, func(ctx context.Context) error {
		qb := r.repository.Movie
		movie, err = qb.UpdatePartial(ctx, movieID, updatedMovie)
		if err != nil {
			return err
		}

		// update image table
		if frontImageIncluded {
			if err := qb.UpdateFrontImage(ctx, movie.ID, frontimageData); err != nil {
				return err
			}
		}

		if backImageIncluded {
			if err := qb.UpdateBackImage(ctx, movie.ID, backimageData); err != nil {
				return err
			}
		}

		return nil
	}); err != nil {
		return nil, err
	}

	r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields())
	return r.getMovie(ctx, movie.ID)
}

func (r *mutationResolver) BulkMovieUpdate(ctx context.Context, input BulkMovieUpdateInput) ([]*models.Movie, error) {
	movieIDs, err := stringslice.StringSliceToIntSlice(input.Ids)
	if err != nil {
		return nil, fmt.Errorf("converting ids: %w", err)
	}

	translator := changesetTranslator{
		inputMap: getUpdateInputMap(ctx),
	}

	// Populate movie from the input
	updatedMovie := models.NewMoviePartial()

	updatedMovie.Rating = translator.optionalInt(input.Rating100, "rating100")
	updatedMovie.Director = translator.optionalString(input.Director, "director")

	updatedMovie.StudioID, err = translator.optionalIntFromString(input.StudioID, "studio_id")
	if err != nil {
		return nil, fmt.Errorf("converting studio id: %w", err)
	}

	ret := []*models.Movie{}

	if err := r.withTxn(ctx, func(ctx context.Context) error {
		qb := r.repository.Movie

		for _, movieID := range movieIDs {
			movie, err := qb.UpdatePartial(ctx, movieID, updatedMovie)
			if err != nil {
				return err
			}

			ret = append(ret, movie)
		}

		return nil
	}); err != nil {
		return nil, err
	}

	var newRet []*models.Movie
	for _, movie := range ret {
		r.hookExecutor.ExecutePostHooks(ctx, movie.ID, hook.MovieUpdatePost, input, translator.getFields())

		movie, err = r.getMovie(ctx, movie.ID)
		if err != nil {
			return nil, err
		}

		newRet = append(newRet, movie)
	}

	return newRet, nil
}

func (r *mutationResolver) MovieDestroy(ctx context.Context, input MovieDestroyInput) (bool, error) {
	id, err := strconv.Atoi(input.ID)
	if err != nil {
		return false, fmt.Errorf("converting id: %w", err)
	}

	if err := r.withTxn(ctx, func(ctx context.Context) error {
		return r.repository.Movie.Destroy(ctx, id)
	}); err != nil {
		return false, err
	}

	r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, input, nil)

	return true, nil
}

func (r *mutationResolver) MoviesDestroy(ctx context.Context, movieIDs []string) (bool, error) {
	ids, err := stringslice.StringSliceToIntSlice(movieIDs)
	if err != nil {
		return false, fmt.Errorf("converting ids: %w", err)
	}

	if err := r.withTxn(ctx, func(ctx context.Context) error {
		qb := r.repository.Movie
		for _, id := range ids {
			if err := qb.Destroy(ctx, id); err != nil {
				return err
			}
		}

		return nil
	}); err != nil {
		return false, err
	}

	for _, id := range ids {
		r.hookExecutor.ExecutePostHooks(ctx, id, hook.MovieDestroyPost, movieIDs, nil)
	}

	return true, nil
}
