package repository

import (
	"context"
	"errors"
	"time"

	"github.com/jackc/pgconn"
	commonrepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	"gorm.io/gorm"
	"gorm.io/gorm/clause"
)

type UserRepository interface {
	commonrepo.BaseRepository[entity.User]
	IdExists(ctx context.Context, id uint) (bool, error)
	GetByIdUser(ctx context.Context, idUser int64, modifier func(*gorm.DB) *gorm.DB) (*entity.User, error)
	UpsertByIdUser(ctx context.Context, user *entity.User) error
	SoftDeleteByIdUser(ctx context.Context, idUser int64) error
}

type UserRepositoryImpl struct {
	*commonrepo.BaseRepositoryImpl[entity.User]
	db *gorm.DB
}

func NewUserRepository(db *gorm.DB) UserRepository {
	return &UserRepositoryImpl{
		BaseRepositoryImpl: commonrepo.NewBaseRepository[entity.User](db),
		db:                 db,
	}
}

func (r *UserRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
	return commonrepo.Exists[entity.User](ctx, r.db, id)
}

func (r *UserRepositoryImpl) GetByIdUser(
	ctx context.Context,
	idUser int64,
	modifier func(*gorm.DB) *gorm.DB,
) (*entity.User, error) {
	return r.BaseRepositoryImpl.First(ctx, func(db *gorm.DB) *gorm.DB {
		return db.Where("id_user::bigint = ?::bigint", idUser)
	})
}

func (r *UserRepositoryImpl) UpsertByIdUser(ctx context.Context, user *entity.User) error {
	if user == nil {
		return gorm.ErrInvalidData
	}

	return r.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
		now := time.Now()
		user.DeletedAt = gorm.DeletedAt{}
		user.UpdatedAt = now

		err := tx.Clauses(clause.OnConflict{
			Columns:   []clause.Column{{Name: "id_user"}},
			UpdateAll: true,
		}).Omit("id", "created_at").Create(user).Error
		if err == nil {
			return nil
		}

		if !isUniqueViolation(err, "users_email_unique") {
			return err
		}

		var existing entity.User
		lockQuery := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("email = ?", user.Email)
		if err := lockQuery.First(&existing).Error; err != nil {
			return err
		}

		user.Id = existing.Id

		updates := map[string]any{
			"id_user":    user.IdUser,
			"email":      user.Email,
			"name":       user.Name,
			"updated_at": now,
			"deleted_at": gorm.DeletedAt{},
		}

		if err := tx.Model(&entity.User{}).Where("id = ?", existing.Id).Updates(updates).Error; err != nil {
			return err
		}

		return nil
	})
}

func (r *UserRepositoryImpl) SoftDeleteByIdUser(ctx context.Context, idUser int64) error {
	query := r.DB().WithContext(ctx).Where("id_user::bigint = ?::bigint", idUser)
	result := query.Delete(&entity.User{})
	if result.Error != nil {
		return result.Error
	}
	if result.RowsAffected == 0 {
		return gorm.ErrRecordNotFound
	}
	return nil
}

func isUniqueViolation(err error, constraint string) bool {
	var pgErr *pgconn.PgError
	if !errors.As(err, &pgErr) {
		return false
	}
	if pgErr.Code != "23505" {
		return false
	}
	if constraint == "" {
		return true
	}
	return pgErr.ConstraintName == constraint
}
