package models

import (
	"context"
	"database/sql"
	"fmt"
	"screenmark/screenmark/.gen/haystack/haystack/model"
	. "screenmark/screenmark/.gen/haystack/haystack/table"

	. "github.com/go-jet/jet/v2/postgres"

	"github.com/google/uuid"
)

type TagModel struct {
	dbPool *sql.DB
}

// Raw dogging SQL is kinda based though?
//
// |            nO, usE OrM!!
//
// |        RAW      -     RAW
// |        SQL     | \    SQL
// |        GOOD   |   \   GOOD
// |              -     -
// |            --       --
// |           --         --
// |       ----      IQ     ----
func (m TagModel) getNonExistantTags(ctx context.Context, userId uuid.UUID, tags []string) ([]string, error) {
	if len(tags) == 0 {
		return tags, nil
	}

	values := ""
	counter := 1
	// big big SQL injection problem here?
	for counter = 1; counter <= len(tags); counter++ {
		values += fmt.Sprintf("($%d),", counter)
	}
	values = values[0 : len(values)-1]

	getNonExistingTags := fmt.Sprintf(`WITH given_tags 
        AS (SELECT given_tags.tag FROM (VALUES `+values+`) AS given_tags (tag)),
        this_user_tags AS 
        (SELECT id, tag FROM haystack.user_tags WHERE user_tags.user_id = $%d)
		SELECT given_tags.tag
		FROM given_tags
		LEFT OUTER JOIN haystack.user_tags ON haystack.user_tags.tag = given_tags.tag
		where user_tags.tag is null`, counter)

	getNonExistingTagsStmt, err := m.dbPool.PrepareContext(ctx, getNonExistingTags)
	defer getNonExistingTagsStmt.Close()

	if err != nil {
		return []string{}, err
	}

	args := make([]any, counter)
	for i, v := range tags {
		args[i] = v
	}
	args[counter-1] = userId.String()

	rows, err := getNonExistingTagsStmt.QueryContext(ctx, args...)
	if err != nil {
		return []string{}, err
	}

	nonExistantTags := make([]string, 0)

	for rows.Next() {
		var tag string
		rows.Scan(&tag)

		nonExistantTags = append(nonExistantTags, tag)
	}

	return nonExistantTags, nil
}

func (m TagModel) Save(ctx context.Context, userId uuid.UUID, tags []string) error {
	tagsToInsert, err := m.getNonExistantTags(ctx, userId, tags)
	if err != nil {
		return err
	}

	if len(tagsToInsert) == 0 {
		return nil
	}

	stmt := UserTags.INSERT(UserTags.UserID, UserTags.Tag)

	for _, tag := range tagsToInsert {
		stmt = stmt.VALUES(UUID(userId), tag)
	}

	_, err = stmt.ExecContext(ctx, m.dbPool)

	return err
}

func (m TagModel) List(ctx context.Context, userId uuid.UUID) ([]model.UserTags, error) {
	listTagsStmt := UserTags.SELECT(UserTags.AllColumns).WHERE(UserTags.UserID.EQ(UUID(userId)))

	userTags := []model.UserTags{}

	err := listTagsStmt.QueryContext(ctx, m.dbPool, &userTags)

	return userTags, err
}

func (m TagModel) SaveToImage(ctx context.Context, imageId uuid.UUID, tags []string) error {
	if len(tags) == 0 {
		return nil
	}

	userId, err := getUserIdFromImage(ctx, m.dbPool, imageId)
	if err != nil {
		return err
	}

	err = m.Save(ctx, userId, tags)
	if err != nil {
		return err
	}

	userTagsExpression := make([]Expression, 0)
	for _, tag := range tags {
		userTagsExpression = append(userTagsExpression, String(tag))
	}

	userTags := make([]model.UserTags, 0)

	getTagsStmt := UserTags.SELECT(
		UserTags.ID, UserTags.Tag,
	).WHERE(UserTags.Tag.IN(userTagsExpression...))
	err = getTagsStmt.Query(m.dbPool, &userTags)
	if err != nil {
		return err
	}

	stmt := ImageTags.INSERT(ImageTags.ImageID, ImageTags.TagID)

	for _, t := range userTags {
		stmt = stmt.VALUES(imageId, t.ID)
	}

	_, err = stmt.ExecContext(ctx, m.dbPool)

	return err
}

func NewTagModel(db *sql.DB) TagModel {
	return TagModel{dbPool: db}
}
