package tag

import (
	"context"
	"fmt"

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

type NameExistsError struct {
	Name string
}

func (e *NameExistsError) Error() string {
	return fmt.Sprintf("tag with name '%s' already exists", e.Name)
}

type NameUsedByAliasError struct {
	Name     string
	OtherTag string
}

func (e *NameUsedByAliasError) Error() string {
	return fmt.Sprintf("name '%s' is used as alias for '%s'", e.Name, e.OtherTag)
}

type InvalidTagHierarchyError struct {
	Direction       string
	CurrentRelation string
	InvalidTag      string
	ApplyingTag     string
	TagPath         string
}

func (e *InvalidTagHierarchyError) Error() string {
	if e.ApplyingTag == "" {
		return fmt.Sprintf("cannot apply tag \"%s\" as a %s of tag as it is already %s", e.InvalidTag, e.Direction, e.CurrentRelation)
	}

	return fmt.Sprintf("cannot apply tag \"%s\" as a %s of \"%s\" as it is already %s (%s)", e.InvalidTag, e.Direction, e.ApplyingTag, e.CurrentRelation, e.TagPath)
}

// EnsureTagNameUnique returns an error if the tag name provided
// is used as a name or alias of another existing tag.
func EnsureTagNameUnique(ctx context.Context, id int, name string, qb models.TagQueryer) error {
	// ensure name is unique
	sameNameTag, err := ByName(ctx, qb, name)
	if err != nil {
		return err
	}

	if sameNameTag != nil && id != sameNameTag.ID {
		return &NameExistsError{
			Name: name,
		}
	}

	// query by alias
	sameNameTag, err = ByAlias(ctx, qb, name)
	if err != nil {
		return err
	}

	if sameNameTag != nil && id != sameNameTag.ID {
		return &NameUsedByAliasError{
			Name:     name,
			OtherTag: sameNameTag.Name,
		}
	}

	return nil
}

func EnsureAliasesUnique(ctx context.Context, id int, aliases []string, qb models.TagQueryer) error {
	for _, a := range aliases {
		if err := EnsureTagNameUnique(ctx, id, a, qb); err != nil {
			return err
		}
	}

	return nil
}

type RelationshipFinder interface {
	FindAllAncestors(ctx context.Context, tagID int, excludeIDs []int) ([]*models.TagPath, error)
	FindAllDescendants(ctx context.Context, tagID int, excludeIDs []int) ([]*models.TagPath, error)
	models.TagRelationLoader
}

func ValidateHierarchyNew(ctx context.Context, parentIDs, childIDs []int, qb RelationshipFinder) error {
	allAncestors := make(map[int]*models.TagPath)
	allDescendants := make(map[int]*models.TagPath)

	for _, parentID := range parentIDs {
		parentsAncestors, err := qb.FindAllAncestors(ctx, parentID, nil)
		if err != nil {
			return err
		}

		for _, ancestorTag := range parentsAncestors {
			allAncestors[ancestorTag.ID] = ancestorTag
		}
	}

	for _, childID := range childIDs {
		childsDescendants, err := qb.FindAllDescendants(ctx, childID, nil)
		if err != nil {
			return err
		}

		for _, descendentTag := range childsDescendants {
			allDescendants[descendentTag.ID] = descendentTag
		}
	}

	// Validate that the tag is not a parent of any of its ancestors
	validateParent := func(testID int) error {
		if parentTag, exists := allDescendants[testID]; exists {
			return &InvalidTagHierarchyError{
				Direction:       "parent",
				CurrentRelation: "a descendant",
				InvalidTag:      parentTag.Name,
				TagPath:         parentTag.Path,
			}
		}

		return nil
	}

	// Validate that the tag is not a child of any of its ancestors
	validateChild := func(testID int) error {
		if childTag, exists := allAncestors[testID]; exists {
			return &InvalidTagHierarchyError{
				Direction:       "child",
				CurrentRelation: "an ancestor",
				InvalidTag:      childTag.Name,
				TagPath:         childTag.Path,
			}
		}

		return nil
	}

	for _, parentID := range parentIDs {
		if err := validateParent(parentID); err != nil {
			return err
		}
	}

	for _, childID := range childIDs {
		if err := validateChild(childID); err != nil {
			return err
		}
	}

	return nil
}

func ValidateHierarchyExisting(ctx context.Context, tag *models.Tag, parentIDs, childIDs []int, qb RelationshipFinder) error {
	allAncestors := make(map[int]*models.TagPath)
	allDescendants := make(map[int]*models.TagPath)

	parentsAncestors, err := qb.FindAllAncestors(ctx, tag.ID, nil)
	if err != nil {
		return err
	}

	for _, ancestorTag := range parentsAncestors {
		allAncestors[ancestorTag.ID] = ancestorTag
	}

	childsDescendants, err := qb.FindAllDescendants(ctx, tag.ID, nil)
	if err != nil {
		return err
	}

	for _, descendentTag := range childsDescendants {
		allDescendants[descendentTag.ID] = descendentTag
	}

	validateParent := func(testID int) error {
		if parentTag, exists := allDescendants[testID]; exists {
			return &InvalidTagHierarchyError{
				Direction:       "parent",
				CurrentRelation: "a descendant",
				InvalidTag:      parentTag.Name,
				ApplyingTag:     tag.Name,
				TagPath:         parentTag.Path,
			}
		}

		return nil
	}

	validateChild := func(testID int) error {
		if childTag, exists := allAncestors[testID]; exists {
			return &InvalidTagHierarchyError{
				Direction:       "child",
				CurrentRelation: "an ancestor",
				InvalidTag:      childTag.Name,
				ApplyingTag:     tag.Name,
				TagPath:         childTag.Path,
			}
		}

		return nil
	}

	for _, parentID := range parentIDs {
		if err := validateParent(parentID); err != nil {
			return err
		}
	}

	for _, childID := range childIDs {
		if err := validateChild(childID); err != nil {
			return err
		}
	}

	return nil
}
