//go:build integration
// +build integration

package sqlite_test

import (
	"context"
	"fmt"
	"strconv"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"github.com/stashapp/stash/pkg/models"
)

func TestMovieFindByName(t *testing.T) {
	withTxn(func(ctx context.Context) error {
		mqb := db.Movie

		name := movieNames[movieIdxWithScene] // find a movie by name

		movie, err := mqb.FindByName(ctx, name, false)

		if err != nil {
			t.Errorf("Error finding movies: %s", err.Error())
		}

		assert.Equal(t, movieNames[movieIdxWithScene], movie.Name)

		name = movieNames[movieIdxWithDupName] // find a movie by name nocase

		movie, err = mqb.FindByName(ctx, name, true)

		if err != nil {
			t.Errorf("Error finding movies: %s", err.Error())
		}
		// movieIdxWithDupName and movieIdxWithScene should have similar names ( only diff should be Name vs NaMe)
		//movie.Name should match with movieIdxWithScene since its ID is before moveIdxWithDupName
		assert.Equal(t, movieNames[movieIdxWithScene], movie.Name)
		//movie.Name should match with movieIdxWithDupName if the check is not case sensitive
		assert.Equal(t, strings.ToLower(movieNames[movieIdxWithDupName]), strings.ToLower(movie.Name))

		return nil
	})
}

func TestMovieFindByNames(t *testing.T) {
	withTxn(func(ctx context.Context) error {
		var names []string

		mqb := db.Movie

		names = append(names, movieNames[movieIdxWithScene]) // find movies by names

		movies, err := mqb.FindByNames(ctx, names, false)
		if err != nil {
			t.Errorf("Error finding movies: %s", err.Error())
		}
		assert.Len(t, movies, 1)
		assert.Equal(t, movieNames[movieIdxWithScene], movies[0].Name)

		movies, err = mqb.FindByNames(ctx, names, true) // find movies by names nocase
		if err != nil {
			t.Errorf("Error finding movies: %s", err.Error())
		}
		assert.Len(t, movies, 2) // movieIdxWithScene and movieIdxWithDupName
		assert.Equal(t, strings.ToLower(movieNames[movieIdxWithScene]), strings.ToLower(movies[0].Name))
		assert.Equal(t, strings.ToLower(movieNames[movieIdxWithScene]), strings.ToLower(movies[1].Name))

		return nil
	})
}

func moviesToIDs(i []*models.Movie) []int {
	ret := make([]int, len(i))
	for i, v := range i {
		ret[i] = v.ID
	}

	return ret
}

func TestMovieQuery(t *testing.T) {
	var (
		frontImage = "front_image"
		backImage  = "back_image"
	)

	tests := []struct {
		name        string
		findFilter  *models.FindFilterType
		filter      *models.MovieFilterType
		includeIdxs []int
		excludeIdxs []int
		wantErr     bool
	}{
		{
			"is missing front image",
			nil,
			&models.MovieFilterType{
				IsMissing: &frontImage,
			},
			// just ensure that it doesn't error
			nil,
			nil,
			false,
		},
		{
			"is missing back image",
			nil,
			&models.MovieFilterType{
				IsMissing: &backImage,
			},
			// just ensure that it doesn't error
			nil,
			nil,
			false,
		},
	}

	for _, tt := range tests {
		runWithRollbackTxn(t, tt.name, func(t *testing.T, ctx context.Context) {
			assert := assert.New(t)

			results, _, err := db.Movie.Query(ctx, tt.filter, tt.findFilter)
			if (err != nil) != tt.wantErr {
				t.Errorf("MovieQueryBuilder.Query() error = %v, wantErr %v", err, tt.wantErr)
				return
			}

			ids := moviesToIDs(results)
			include := indexesToIDs(performerIDs, tt.includeIdxs)
			exclude := indexesToIDs(performerIDs, tt.excludeIdxs)

			for _, i := range include {
				assert.Contains(ids, i)
			}
			for _, e := range exclude {
				assert.NotContains(ids, e)
			}
		})
	}
}

func TestMovieQueryStudio(t *testing.T) {
	withTxn(func(ctx context.Context) error {
		mqb := db.Movie
		studioCriterion := models.HierarchicalMultiCriterionInput{
			Value: []string{
				strconv.Itoa(studioIDs[studioIdxWithMovie]),
			},
			Modifier: models.CriterionModifierIncludes,
		}

		movieFilter := models.MovieFilterType{
			Studios: &studioCriterion,
		}

		movies, _, err := mqb.Query(ctx, &movieFilter, nil)
		if err != nil {
			t.Errorf("Error querying movie: %s", err.Error())
		}

		assert.Len(t, movies, 1)

		// ensure id is correct
		assert.Equal(t, movieIDs[movieIdxWithStudio], movies[0].ID)

		studioCriterion = models.HierarchicalMultiCriterionInput{
			Value: []string{
				strconv.Itoa(studioIDs[studioIdxWithMovie]),
			},
			Modifier: models.CriterionModifierExcludes,
		}

		q := getMovieStringValue(movieIdxWithStudio, titleField)
		findFilter := models.FindFilterType{
			Q: &q,
		}

		movies, _, err = mqb.Query(ctx, &movieFilter, &findFilter)
		if err != nil {
			t.Errorf("Error querying movie: %s", err.Error())
		}
		assert.Len(t, movies, 0)

		return nil
	})
}

func TestMovieQueryURL(t *testing.T) {
	const sceneIdx = 1
	movieURL := getMovieStringValue(sceneIdx, urlField)

	urlCriterion := models.StringCriterionInput{
		Value:    movieURL,
		Modifier: models.CriterionModifierEquals,
	}

	filter := models.MovieFilterType{
		URL: &urlCriterion,
	}

	verifyFn := func(n *models.Movie) {
		t.Helper()
		verifyString(t, n.URL, urlCriterion)
	}

	verifyMovieQuery(t, filter, verifyFn)

	urlCriterion.Modifier = models.CriterionModifierNotEquals
	verifyMovieQuery(t, filter, verifyFn)

	urlCriterion.Modifier = models.CriterionModifierMatchesRegex
	urlCriterion.Value = "movie_.*1_URL"
	verifyMovieQuery(t, filter, verifyFn)

	urlCriterion.Modifier = models.CriterionModifierNotMatchesRegex
	verifyMovieQuery(t, filter, verifyFn)

	urlCriterion.Modifier = models.CriterionModifierIsNull
	urlCriterion.Value = ""
	verifyMovieQuery(t, filter, verifyFn)

	urlCriterion.Modifier = models.CriterionModifierNotNull
	verifyMovieQuery(t, filter, verifyFn)
}

func verifyMovieQuery(t *testing.T, filter models.MovieFilterType, verifyFn func(s *models.Movie)) {
	withTxn(func(ctx context.Context) error {
		t.Helper()
		sqb := db.Movie

		movies := queryMovie(ctx, t, sqb, &filter, nil)

		// assume it should find at least one
		assert.Greater(t, len(movies), 0)

		for _, m := range movies {
			verifyFn(m)
		}

		return nil
	})
}

func queryMovie(ctx context.Context, t *testing.T, sqb models.MovieReader, movieFilter *models.MovieFilterType, findFilter *models.FindFilterType) []*models.Movie {
	movies, _, err := sqb.Query(ctx, movieFilter, findFilter)
	if err != nil {
		t.Errorf("Error querying movie: %s", err.Error())
	}

	return movies
}

func TestMovieQuerySorting(t *testing.T) {
	sort := "scenes_count"
	direction := models.SortDirectionEnumDesc
	findFilter := models.FindFilterType{
		Sort:      &sort,
		Direction: &direction,
	}

	withTxn(func(ctx context.Context) error {
		sqb := db.Movie
		movies := queryMovie(ctx, t, sqb, nil, &findFilter)

		// scenes should be in same order as indexes
		firstMovie := movies[0]

		assert.Equal(t, movieIDs[movieIdxWithScene], firstMovie.ID)

		// sort in descending order
		direction = models.SortDirectionEnumAsc

		movies = queryMovie(ctx, t, sqb, nil, &findFilter)
		lastMovie := movies[len(movies)-1]

		assert.Equal(t, movieIDs[movieIdxWithScene], lastMovie.ID)

		return nil
	})
}

func TestMovieUpdateFrontImage(t *testing.T) {
	if err := withRollbackTxn(func(ctx context.Context) error {
		qb := db.Movie

		// create movie to test against
		const name = "TestMovieUpdateMovieImages"
		movie := models.Movie{
			Name: name,
		}
		err := qb.Create(ctx, &movie)
		if err != nil {
			return fmt.Errorf("Error creating movie: %s", err.Error())
		}

		return testUpdateImage(t, ctx, movie.ID, qb.UpdateFrontImage, qb.GetFrontImage)
	}); err != nil {
		t.Error(err.Error())
	}
}

func TestMovieUpdateBackImage(t *testing.T) {
	if err := withRollbackTxn(func(ctx context.Context) error {
		qb := db.Movie

		// create movie to test against
		const name = "TestMovieUpdateMovieImages"
		movie := models.Movie{
			Name: name,
		}
		err := qb.Create(ctx, &movie)
		if err != nil {
			return fmt.Errorf("Error creating movie: %s", err.Error())
		}

		return testUpdateImage(t, ctx, movie.ID, qb.UpdateBackImage, qb.GetBackImage)
	}); err != nil {
		t.Error(err.Error())
	}
}

// TODO Update
// TODO Destroy - ensure image is destroyed
// TODO Find
// TODO Count
// TODO All
// TODO Query
