package models

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

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

	"github.com/google/uuid"
)

type LocationModel struct {
	dbPool *sql.DB
}

func (m LocationModel) List(ctx context.Context, userId uuid.UUID) ([]model.Locations, error) {
	listLocationsStmt := SELECT(Locations.AllColumns).
		FROM(
			Locations.
				INNER_JOIN(UserLocations, UserLocations.LocationID.EQ(Locations.ID)),
		).
		WHERE(UserLocations.UserID.EQ(UUID(userId)))

	locations := []model.Locations{}

	err := listLocationsStmt.QueryContext(ctx, m.dbPool, &locations)
	return locations, err
}

func (m LocationModel) Get(ctx context.Context, locationId uuid.UUID) (model.Locations, error) {
	getLocationStmt := Locations.
		SELECT(Locations.AllColumns).
		WHERE(Locations.ID.EQ(UUID(locationId)))

	location := model.Locations{}
	err := getLocationStmt.QueryContext(ctx, m.dbPool, &location)

	return location, err
}

func (m LocationModel) Update(ctx context.Context, location model.Locations) (model.Locations, error) {
	existingLocation, err := m.Get(ctx, location.ID)
	if err != nil {
		return model.Locations{}, err
	}

	existingLocation.Name = location.Name

	if location.Description != nil {
		existingLocation.Description = location.Description
	}

	if location.Address != nil {
		existingLocation.Address = location.Address
	}

	updateLocationStmt := Locations.
		UPDATE(Locations.MutableColumns).
		MODEL(existingLocation).
		WHERE(Locations.ID.EQ(UUID(location.ID))).
		RETURNING(Locations.AllColumns)

	updatedLocation := model.Locations{}
	err = updateLocationStmt.QueryContext(ctx, m.dbPool, &updatedLocation)

	return updatedLocation, err
}

func (m LocationModel) Save(ctx context.Context, userId uuid.UUID, location model.Locations) (model.Locations, error) {
	if location.ID != uuid.Nil {
		return m.Update(ctx, location)
	}

	insertLocationStmt := Locations.
		INSERT(Locations.Name, Locations.Address, Locations.Description).
		VALUES(location.Name, location.Address, location.Description).
		RETURNING(Locations.AllColumns)

	insertedLocation := model.Locations{}
	err := insertLocationStmt.QueryContext(ctx, m.dbPool, &insertedLocation)
	if err != nil {
		return model.Locations{}, err
	}

	insertUserLocationStmt := UserLocations.
		INSERT(UserLocations.UserID, UserLocations.LocationID).
		VALUES(userId, insertedLocation.ID)

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

	return insertedLocation, err
}

func (m LocationModel) SaveToImage(ctx context.Context, imageId uuid.UUID, locationId uuid.UUID) (model.ImageLocations, error) {
	imageLocation := model.ImageLocations{}

	checkExistingStmt := ImageLocations.
		SELECT(ImageLocations.AllColumns).
		WHERE(
			ImageLocations.ImageID.EQ(UUID(imageId)).
				AND(ImageLocations.LocationID.EQ(UUID(locationId))),
		)

	err := checkExistingStmt.QueryContext(ctx, m.dbPool, &imageLocation)
	if err != nil && err != qrm.ErrNoRows {
		// A real error
		return model.ImageLocations{}, err
	}

	if err == nil {
		// Already exists.
		return imageLocation, nil
	}

	insertImageLocationStmt := ImageLocations.
		INSERT(ImageLocations.ImageID, ImageLocations.LocationID).
		VALUES(imageId, locationId).
		RETURNING(ImageLocations.AllColumns)

	err = insertImageLocationStmt.QueryContext(ctx, m.dbPool, &imageLocation)

	return imageLocation, err
}

func NewLocationModel(db *sql.DB) LocationModel {
	return LocationModel{dbPool: db}
}
