package services

import (
	"context"
	"encoding/json"
	"fmt"
	"history-api/internal/dtos/request"
	"history-api/internal/dtos/response"
	"history-api/internal/gen/sqlc"
	"history-api/internal/models"
	"history-api/internal/repositories"
	"history-api/pkg/ai"
	"history-api/pkg/cache"
	"history-api/pkg/constants"
	"history-api/pkg/convert"
	"slices"
	"strconv"

	"github.com/gofiber/fiber/v3"
	"github.com/jackc/pgx/v5/pgtype"
	"github.com/jackc/pgx/v5/pgxpool"
	"github.com/rs/zerolog/log"
	"golang.org/x/sync/errgroup"
)

type SubmissionService interface {
	CreateSubmission(ctx context.Context, userID string, dto *request.CreateSubmissionDto) (*response.SubmissionResponse, *fiber.Error)
	UpdateSubmissionStatus(ctx context.Context, reviewerID string, submissionID string, dto *request.UpdateSubmissionStatusDto) (*response.SubmissionResponse, *fiber.Error)
	GetSubmissionByID(ctx context.Context, id string) (*response.SubmissionResponse, *fiber.Error)
	SearchSubmissions(ctx context.Context, dto *request.SearchSubmissionDto) (*response.PaginatedResponse, *fiber.Error)
	DeleteSubmission(ctx context.Context, userID string, id string, claims *response.JWTClaims) *fiber.Error
}

type submissionService struct {
	submissionRepo   repositories.SubmissionRepository
	projectRepo      repositories.ProjectRepository
	commitRepo       repositories.CommitRepository
	userRepo         repositories.UserRepository
	wikiRepo         repositories.WikiRepository
	geometryRepo     repositories.GeometryRepository
	entityRepo       repositories.EntityRepository
	battleReplayRepo repositories.BattleReplayRepository
	ragRepo          repositories.RagRepository
	ragUtils         *ai.RagUtils
	db               *pgxpool.Pool
	c                cache.Cache
}

func NewSubmissionService(
	submissionRepo repositories.SubmissionRepository,
	projectRepo repositories.ProjectRepository,
	commitRepo repositories.CommitRepository,
	userRepo repositories.UserRepository,
	wikiRepo repositories.WikiRepository,
	geometryRepo repositories.GeometryRepository,
	entityRepo repositories.EntityRepository,
	battleReplayRepo repositories.BattleReplayRepository,
	ragRepo repositories.RagRepository,
	ragUtils *ai.RagUtils,
	db *pgxpool.Pool,
	c cache.Cache,
) SubmissionService {
	return &submissionService{
		submissionRepo:   submissionRepo,
		projectRepo:      projectRepo,
		commitRepo:       commitRepo,
		userRepo:         userRepo,
		wikiRepo:         wikiRepo,
		geometryRepo:     geometryRepo,
		entityRepo:       entityRepo,
		battleReplayRepo: battleReplayRepo,
		ragRepo:          ragRepo,
		ragUtils:         ragUtils,
		db:               db,
		c:                c,
	}
}

func (s *submissionService) CreateSubmission(ctx context.Context, userID string, dto *request.CreateSubmissionDto) (*response.SubmissionResponse, *fiber.Error) {
	projectUUID, err := convert.StringToUUID(dto.ProjectID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project ID")
	}

	commitUUID, err := convert.StringToUUID(dto.CommitID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid commit ID")
	}

	userUUID, err := convert.StringToUUID(userID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid user ID")
	}

	commit, err := s.commitRepo.GetByID(ctx, commitUUID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusNotFound, "Commit not found")
	}
	if commit.ProjectID != dto.ProjectID {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Commit does not belong to project")
	}

	var snapshotData request.CommitSnapshot
	if err := json.Unmarshal(commit.SnapshotJson, &snapshotData); err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
	}

	var entitySlugs []string
	entitySlugToID := make(map[string]string)
	for _, entity := range snapshotData.Entities {
		if entity.Source == "inline" && entity.Slug != nil {
			entitySlugs = append(entitySlugs, *entity.Slug)
			entitySlugToID[*entity.Slug] = entity.ID
		}
	}

	if len(entitySlugs) > 0 {
		existEntities, err := s.entityRepo.GetBySlugs(ctx, entitySlugs)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get entities")
		}
		for _, exist := range existEntities {
			if snapID, ok := entitySlugToID[exist.Slug]; ok {
				if exist.ID != snapID {
					return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Entity %s already exists", exist.Slug))
				}
			}
		}
	}

	var wikiSlugs []string
	wikiSlugToID := make(map[string]string)
	for _, wiki := range snapshotData.Wikis {
		if wiki.Source == "inline" && wiki.Slug != nil {
			wikiSlugs = append(wikiSlugs, *wiki.Slug)
			wikiSlugToID[*wiki.Slug] = wiki.ID
		}
	}

	if len(wikiSlugs) > 0 {
		existWikis, err := s.wikiRepo.GetBySlugs(ctx, wikiSlugs)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wikis")
		}
		for _, exist := range existWikis {
			if snapID, ok := wikiSlugToID[exist.Slug]; ok {
				if exist.ID != snapID {
					return nil, fiber.NewError(fiber.StatusConflict, fmt.Sprintf("Wiki %s already exists", exist.Slug))
				}
			}
		}
	}

	project, err := s.projectRepo.GetByID(ctx, projectUUID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusNotFound, "Project not found")
	}

	for _, sub := range project.Submissions {
		if sub.Status == constants.StatusTypePending {
			return nil, fiber.NewError(fiber.StatusConflict, "There is already a pending submission for this project")
		}
	}

	arg := sqlc.CreateSubmissionParams{
		ProjectID: projectUUID,
		CommitID:  commitUUID,
		UserID:    userUUID,
		Status:    constants.StatusTypePending.Int16(),
		Content:   convert.StringToText(dto.Content),
	}

	submission, err := s.submissionRepo.Create(ctx, arg)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create submission")
	}

	_ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", project.ID))

	return submission.ToResponse(), nil
}

func (s *submissionService) UpdateSubmissionStatus(ctx context.Context, reviewerID string, submissionID string, dto *request.UpdateSubmissionStatusDto) (*response.SubmissionResponse, *fiber.Error) {
	tx, err := s.db.Begin(ctx)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to start transaction")
	}
	defer tx.Rollback(ctx)

	submissionRepo := s.submissionRepo.WithTx(tx)
	commitRepo := s.commitRepo.WithTx(tx)
	entityRepo := s.entityRepo.WithTx(tx)
	geometryRepo := s.geometryRepo.WithTx(tx)
	wikiRepo := s.wikiRepo.WithTx(tx)
	battleReplayRepo := s.battleReplayRepo.WithTx(tx)

	submissionUUID, err := convert.StringToUUID(submissionID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid submission ID")
	}

	reviewerUUID, err := convert.StringToUUID(reviewerID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid reviewer ID")
	}

	status := constants.ParseStatusTypeText(dto.Status)
	if status == constants.StatusTypeUnknown {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid status")
	}

	submission, err := s.submissionRepo.GetByID(ctx, submissionUUID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusNotFound, "Submission not found")
	}

	if submission.Status != constants.StatusTypePending {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Submission already processed")
	}

	commitUUID, err := convert.StringToUUID(submission.CommitID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid commit ID")
	}

	commit, err := s.commitRepo.GetByID(ctx, commitUUID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusNotFound, "Commit not found")
	}

	if commit.ProjectID != submission.ProjectID {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Commit does not belong to project")
	}

	listDeleteEntities := make([]pgtype.UUID, 0)
	listDeleteWikis := make([]pgtype.UUID, 0)
	listDeleteGeometries := make([]pgtype.UUID, 0)
	listDeleteBattleReplays := make([]pgtype.UUID, 0)
	var snapshotData request.CommitSnapshot
	err = json.Unmarshal(commit.SnapshotJson, &snapshotData)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to parse commit snapshot")
	}

	if status == constants.StatusTypeApproved {
		projectUUID, err := convert.StringToUUID(commit.ProjectID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid project ID")
		}
		currentEntity, err := s.entityRepo.GetByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusNotFound, "Entity not found: "+err.Error())
		}

		currentGeometry, err := s.geometryRepo.GetByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusNotFound, "Geometry not found: "+err.Error())
		}

		currentWiki, err := s.wikiRepo.GetByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusNotFound, "Wiki not found: "+err.Error())
		}

		currentBattleReplay, err := s.battleReplayRepo.GetByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusNotFound, "Battle replay not found: "+err.Error())
		}

		persistItemIDs := make(map[string]struct{})
		for _, item := range snapshotData.Entities {
			persistItemIDs[item.ID] = struct{}{}
		}
		for _, item := range snapshotData.Geometries {
			persistItemIDs[item.ID] = struct{}{}
		}
		for _, item := range snapshotData.Wikis {
			persistItemIDs[item.ID] = struct{}{}
		}
		for _, item := range snapshotData.Replays {
			persistItemIDs[item.ID] = struct{}{}
		}

		persistCurrentItemIDs := make(map[string]struct{})
		for _, item := range currentEntity {
			persistCurrentItemIDs[item.ID] = struct{}{}
		}
		for _, item := range currentGeometry {
			persistCurrentItemIDs[item.ID] = struct{}{}
		}
		for _, item := range currentWiki {
			persistCurrentItemIDs[item.ID] = struct{}{}
		}
		for _, item := range currentBattleReplay {
			persistCurrentItemIDs[item.ID] = struct{}{}
		}

		for _, e := range currentEntity {
			if _, ok := persistItemIDs[e.ID]; !ok {
				itemUUID, err := convert.StringToUUID(e.ID)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid entity ID")
				}
				listDeleteEntities = append(listDeleteEntities, itemUUID)
				delete(persistCurrentItemIDs, e.ID)
			}
		}

		for _, g := range currentGeometry {
			if _, ok := persistItemIDs[g.ID]; !ok {
				itemUUID, err := convert.StringToUUID(g.ID)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID")
				}
				listDeleteGeometries = append(listDeleteGeometries, itemUUID)
				delete(persistCurrentItemIDs, g.ID)
			}
		}

		for _, w := range currentWiki {
			if _, ok := persistItemIDs[w.ID]; !ok {
				itemUUID, err := convert.StringToUUID(w.ID)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid wiki ID")
				}
				listDeleteWikis = append(listDeleteWikis, itemUUID)
				delete(persistCurrentItemIDs, w.ID)
			}
		}

		for _, br := range currentBattleReplay {
			if _, ok := persistItemIDs[br.ID]; !ok {
				itemUUID, err := convert.StringToUUID(br.ID)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid battle replay ID")
				}
				listDeleteBattleReplays = append(listDeleteBattleReplays, itemUUID)
				delete(persistCurrentItemIDs, br.ID)
			}
		}

		if len(listDeleteEntities) > 0 {
			if err = entityRepo.DeleteByIDs(ctx, listDeleteEntities); err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete entities")
			}
		}

		if len(listDeleteGeometries) > 0 {
			if err = geometryRepo.DeleteByIDs(ctx, listDeleteGeometries); err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete geometries")
			}
		}

		if len(listDeleteWikis) > 0 {
			if err = wikiRepo.DeleteByIDs(ctx, listDeleteWikis); err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete wikis")
			}
		}

		if len(listDeleteBattleReplays) > 0 {
			if err = battleReplayRepo.DeleteByIDs(ctx, listDeleteBattleReplays); err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete battle replays")
			}
		}

		refEntityIDs := []string{}
		for _, e := range snapshotData.Entities {
			if e.Source == "ref" {
				refEntityIDs = append(refEntityIDs, e.ID)
			}
		}

		refEntities, _ := s.entityRepo.GetByIDs(ctx, refEntityIDs)
		refEntityMap := make(map[string]bool)
		for _, e := range refEntities {
			refEntityMap[e.ID] = true
		}

		newEntities := make([]*request.EntitySnapshot, 0, len(snapshotData.Entities))
		for i, entity := range snapshotData.Entities {
			if entity.Operation == "delete" {
				continue
			}

			entityUUID, err := convert.StringToUUID(entity.ID)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid entity ID")
			}

			if _, ok := persistCurrentItemIDs[entity.ID]; ok {
				_, err := entityRepo.Update(ctx, sqlc.UpdateEntityParams{
					Name:        convert.StringToText(entity.Name),
					Description: convert.StringToText(entity.Description),
					Slug:        convert.PtrToText(entity.Slug),
					Status:      convert.PtrToInt2(entity.Status),
					TimeStart:   convert.PtrFloat64ToInt4(entity.TimeStart),
					TimeEnd:     convert.PtrFloat64ToInt4(entity.TimeEnd),
					ID:          entityUUID,
				})

				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update entity: "+err.Error())
				}

				newEntities = append(newEntities, snapshotData.Entities[i])

			} else if entity.Source == "inline" {
				_, err := entityRepo.Create(ctx, sqlc.CreateEntityParams{
					ID:          entityUUID,
					Name:        entity.Name,
					Description: convert.StringToText(entity.Description),
					ProjectID:   projectUUID,
					Slug:        convert.PtrToText(entity.Slug),
					Status:      convert.PtrToInt2(entity.Status),
					TimeStart:   convert.PtrFloat64ToInt4(entity.TimeStart),
					TimeEnd:     convert.PtrFloat64ToInt4(entity.TimeEnd),
				})

				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create entity: "+err.Error())
				}

				newEntities = append(newEntities, snapshotData.Entities[i])

			} else if entity.Source == "ref" {
				if !refEntityMap[entity.ID] {
					continue
				}
				newEntities = append(newEntities, snapshotData.Entities[i])
			}
		}
		snapshotData.Entities = newEntities

		refGeometryIDs := []string{}
		for _, g := range snapshotData.Geometries {
			if g.Source == "ref" {
				refGeometryIDs = append(refGeometryIDs, g.ID)
			}
		}
		refGeometries, _ := s.geometryRepo.GetByIDs(ctx, refGeometryIDs)
		refGeometryMap := make(map[string]bool)
		for _, g := range refGeometries {
			refGeometryMap[g.ID] = true
		}

		newGeometries := make([]*request.GeometrySnapshot, 0, len(snapshotData.Geometries))
		for i, geo := range snapshotData.Geometries {
			if geo.Operation == "delete" {
				continue
			}

			geometryUUID, err := convert.StringToUUID(geo.ID)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID")
			}

			binding, _ := json.Marshal(geo.Binding)
			geoTypeCode := int16(0)
			if geo.Type != "" {
				if n, err := strconv.ParseInt(geo.Type, 10, 16); err == nil {
					geoTypeCode = int16(n)
				}
			}

			if _, ok := persistCurrentItemIDs[geo.ID]; ok {
				params := sqlc.UpdateGeometryParams{
					ID:           geometryUUID,
					GeoType:      pgtype.Int2{Int16: geoTypeCode, Valid: true},
					DrawGeometry: geo.DrawGeometry,
					Binding:      binding,
					TimeStart:    convert.PtrFloat64ToInt4(geo.TimeStart),
					TimeEnd:      convert.PtrFloat64ToInt4(geo.TimeEnd),
					ProjectID:    projectUUID,
				}

				if geo.BBox != nil {
					params.UpdateBbox = pgtype.Bool{Bool: true, Valid: true}
					params.MinLng = pgtype.Float8{Float64: geo.BBox.MinLng, Valid: true}
					params.MinLat = pgtype.Float8{Float64: geo.BBox.MinLat, Valid: true}
					params.MaxLng = pgtype.Float8{Float64: geo.BBox.MaxLng, Valid: true}
					params.MaxLat = pgtype.Float8{Float64: geo.BBox.MaxLat, Valid: true}
				}

				_, err := geometryRepo.Update(ctx, params)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update geometry: "+err.Error())
				}
				newGeometries = append(newGeometries, snapshotData.Geometries[i])

			} else if geo.Source == "inline" {
				params := sqlc.CreateGeometryParams{
					ID:           geometryUUID,
					GeoType:      geoTypeCode,
					DrawGeometry: geo.DrawGeometry,
					Binding:      binding,
					TimeStart:    convert.PtrFloat64ToInt4(geo.TimeStart),
					TimeEnd:      convert.PtrFloat64ToInt4(geo.TimeEnd),
					ProjectID:    projectUUID,
				}
				if geo.BBox != nil {
					params.MinLng = geo.BBox.MinLng
					params.MinLat = geo.BBox.MinLat
					params.MaxLng = geo.BBox.MaxLng
					params.MaxLat = geo.BBox.MaxLat
				}

				_, err := geometryRepo.Create(ctx, params)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create geometry: "+err.Error())
				}
				newGeometries = append(newGeometries, snapshotData.Geometries[i])

			} else if geo.Source == "ref" {
				if !refGeometryMap[geo.ID] {
					continue
				}
				newGeometries = append(newGeometries, snapshotData.Geometries[i])
			}
		}
		snapshotData.Geometries = newGeometries

		refWikiIDs := []string{}
		for _, w := range snapshotData.Wikis {
			if w.Source == "ref" {
				refWikiIDs = append(refWikiIDs, w.ID)
			}
		}
		refWikis, _ := s.wikiRepo.GetByIDs(ctx, refWikiIDs)
		refWikiMap := make(map[string]bool)
		for _, w := range refWikis {
			refWikiMap[w.ID] = true
		}

		newWikis := make([]*request.WikiSnapshot, 0, len(snapshotData.Wikis))
		for i, wiki := range snapshotData.Wikis {
			if wiki.Operation == "delete" {
				continue
			}

			wikiUUID, err := convert.StringToUUID(wiki.ID)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid wiki ID")
			}

			if _, ok := persistCurrentItemIDs[wiki.ID]; ok {
				_, err := wikiRepo.Update(ctx, sqlc.UpdateWikiParams{
					ID:        wikiUUID,
					Title:     convert.StringToText(wiki.Title),
					Slug:      convert.PtrToText(wiki.Slug),
					ProjectID: projectUUID,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update wiki: "+err.Error())
				}

				count, err := s.wikiRepo.GetContentCountByWikiID(ctx, wikiUUID)
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to get wiki content count: "+err.Error())
				}
				versionTitle := fmt.Sprintf("Version %d", count+1)

				_, err = wikiRepo.CreateContent(ctx, sqlc.CreateWikiContentParams{
					WikiID:  wikiUUID,
					Title:   versionTitle,
					Content: convert.StringToText(wiki.Doc),
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error())
				}

				_ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug))

				newWikis = append(newWikis, snapshotData.Wikis[i])

			} else if wiki.Source == "inline" {
				_, err := wikiRepo.Create(ctx, sqlc.CreateWikiParams{
					ID:        wikiUUID,
					Title:     convert.StringToText(wiki.Title),
					Slug:      convert.PtrToText(wiki.Slug),
					ProjectID: projectUUID,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki: "+err.Error())
				}

				_, err = wikiRepo.CreateContent(ctx, sqlc.CreateWikiContentParams{
					WikiID:  wikiUUID,
					Title:   "Version 1",
					Content: convert.StringToText(wiki.Doc),
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki content: "+err.Error())
				}

				_ = s.c.Del(ctx, fmt.Sprintf("wiki:id:%s", wikiUUID.String()), fmt.Sprintf("wiki:slug:%s", *wiki.Slug))

				newWikis = append(newWikis, snapshotData.Wikis[i])

			} else if wiki.Source == "ref" {
				if !refWikiMap[wiki.ID] {
					continue
				}
				newWikis = append(newWikis, snapshotData.Wikis[i])
			}
		}
		snapshotData.Wikis = newWikis

		for _, replay := range snapshotData.Replays {
			replayUUID, err := convert.StringToUUID(replay.ID)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid battle replay ID")
			}

			geomUUID, err := convert.StringToUUID(replay.GeometryID)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Invalid geometry ID in battle replay")
			}

			targetIDs, err := json.Marshal(replay.TargetGeometryIDs)
			if err != nil {
				return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal target geometry IDs")
			}

			if _, ok := persistCurrentItemIDs[replay.ID]; ok {
				_, err := battleReplayRepo.Update(ctx, sqlc.UpdateBattleReplayParams{
					ID:                replayUUID,
					GeometryID:        geomUUID,
					TargetGeometryIds: targetIDs,
					Detail:            replay.Detail,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update battle replay: "+err.Error())
				}
			} else {
				_, err := battleReplayRepo.Create(ctx, sqlc.CreateBattleReplayParams{
					ID:                replayUUID,
					GeometryID:        geomUUID,
					ProjectID:         projectUUID,
					TargetGeometryIds: targetIDs,
					Detail:            replay.Detail,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create battle replay: "+err.Error())
				}
			}
		}

		err = geometryRepo.DeleteEntityGeometriesByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete geometry entity: "+err.Error())
		}
		err = wikiRepo.DeleteEntityWikisByProjectID(ctx, projectUUID)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to delete wiki entity: "+err.Error())
		}

		validEntities := make(map[string]bool)
		for _, e := range snapshotData.Entities {
			validEntities[e.ID] = true
		}
		validGeometries := make(map[string]bool)
		for _, g := range snapshotData.Geometries {
			validGeometries[g.ID] = true
		}
		validWikis := make(map[string]bool)
		for _, w := range snapshotData.Wikis {
			validWikis[w.ID] = true
		}

		if len(snapshotData.GeometryEntity) > 0 {
			geomLinks := make(map[string][]pgtype.UUID)
			for _, link := range snapshotData.GeometryEntity {
				if !validEntities[link.EntityID] || !validGeometries[link.GeometryID] {
					continue
				}
				gID, _ := convert.StringToUUID(link.GeometryID)
				geomLinks[link.EntityID] = append(geomLinks[link.EntityID], gID)
			}

			for eIDStr, gIDs := range geomLinks {
				eID, _ := convert.StringToUUID(eIDStr)
				err = geometryRepo.CreateEntityGeometries(ctx, sqlc.CreateEntityGeometriesParams{
					EntityID:    eID,
					GeometryIds: gIDs,
					ProjectID:   projectUUID,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create geometry entity: "+err.Error())
				}
			}
		}

		if len(snapshotData.EntityWiki) > 0 {
			wikiLinks := make(map[string][]pgtype.UUID)
			for _, link := range snapshotData.EntityWiki {
				if link.Operation == "delete" || (link.IsDeleted != nil && *link.IsDeleted == 1) {
					continue
				}
				if !validEntities[link.EntityID] || !validWikis[link.WikiID] {
					continue
				}
				wID, _ := convert.StringToUUID(link.WikiID)
				wikiLinks[link.EntityID] = append(wikiLinks[link.EntityID], wID)
			}

			for eIDStr, wIDs := range wikiLinks {
				eID, _ := convert.StringToUUID(eIDStr)
				err = wikiRepo.CreateEntityWikis(ctx, sqlc.CreateEntityWikisParams{
					EntityID:  eID,
					WikiIds:   wIDs,
					ProjectID: projectUUID,
				})
				if err != nil {
					return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to create wiki entity: "+err.Error())
				}
			}
		}

		newSnapshot, err := json.Marshal(snapshotData)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to marshal snapshot")
		}
		_, err = commitRepo.UpdateSnapshot(ctx, commitUUID, newSnapshot)
		if err != nil {
			return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update snapshot: "+err.Error())
		}
	}

	if status == constants.StatusTypeApproved {
		wikiDeleteIDs := make([]string, 0)
		entityDeleteIDs := make([]string, 0)

		for _, id := range listDeleteWikis {
			wikiDeleteIDs = append(wikiDeleteIDs, convert.UUIDToString(id))
		}
		for _, id := range listDeleteEntities {
			entityDeleteIDs = append(entityDeleteIDs, convert.UUIDToString(id))
		}

		for _, wiki := range snapshotData.Wikis {
			if wiki.Operation == "delete" {
				wikiDeleteIDs = append(wikiDeleteIDs, wiki.ID)
			}
		}
		for _, entity := range snapshotData.Entities {
			if entity.Operation == "delete" {
				entityDeleteIDs = append(entityDeleteIDs, entity.ID)
			}
		}

		ragTask := models.RagIndexTask{
			ProjectID:       commit.ProjectID,
			DeleteWikiIDs:   wikiDeleteIDs,
			DeleteEntityIDs: entityDeleteIDs,
		}

		for _, wiki := range snapshotData.Wikis {
			ragTask.Wikis = append(ragTask.Wikis, &models.RagWikiItem{
				ID:     wiki.ID,
				Title:  wiki.Title,
				Doc:    wiki.Doc,
				Source: wiki.Source,
			})
		}
		for _, entity := range snapshotData.Entities {
			ragTask.Entities = append(ragTask.Entities, &models.RagEntityItem{
				ID:          entity.ID,
				Name:        entity.Name,
				Description: entity.Description,
				Source:      entity.Source,
			})
		}

		if err := s.c.PublishTask(ctx, constants.StreamRagName, constants.TaskTypeRagIndexSubmission, ragTask); err != nil {
			log.Error().Err(err).Str("project_id", commit.ProjectID).Msg("Failed to publish RAG index task")
		}
	}

	arg := sqlc.UpdateSubmissionParams{
		ID:         submissionUUID,
		Status:     pgtype.Int2{Int16: status.Int16(), Valid: true},
		ReviewedBy: reviewerUUID,
		ReviewNote: convert.StringToText(dto.ReviewNote),
	}

	updatedSubmission, err := submissionRepo.Update(ctx, arg)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to update submission status: "+err.Error())
	}

	err = tx.Commit(ctx)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to commit transaction: "+err.Error())
	}

	if status == constants.StatusTypeApproved {
		go func() {
			bgCtx := context.Background()
			_ = s.c.DelByPattern(bgCtx, "entity:search*")
			_ = s.c.DelByPattern(bgCtx, "geometry:search*")
			_ = s.c.DelByPattern(bgCtx, "geometry:search:entity*")
			_ = s.c.DelByPattern(bgCtx, "wiki:search*")
		}()
	}

	_ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", submission.ProjectID))

	return updatedSubmission.ToResponse(), nil
}

func (m *submissionService) fillSearchArgs(arg *sqlc.SearchSubmissionsParams, dto *request.SearchSubmissionDto) {
	if dto.Sort != "" {
		arg.Sort = pgtype.Text{String: dto.Sort, Valid: true}
	} else {
		arg.Sort = pgtype.Text{String: "id", Valid: true}
	}

	arg.Order = pgtype.Text{String: "asc", Valid: true}
	if dto.Order == "desc" {
		arg.Order = pgtype.Text{String: "desc", Valid: true}
	}

	if len(dto.Statuses) > 0 {
		for _, id := range dto.Statuses {
			if u := constants.ParseStatusTypeText(id); u != constants.StatusTypeUnknown {
				arg.Statuses = append(arg.Statuses, u.Int16())
			}
		}
	}

	if len(dto.UserIDs) > 0 {
		for _, id := range dto.UserIDs {
			if u, err := convert.StringToUUID(id); err == nil {
				arg.UserIds = append(arg.UserIds, u)
			}
		}
	}

	if dto.ReviewedBy != nil {
		if rvID, err := convert.StringToUUID(*dto.ReviewedBy); err == nil {
			arg.ReviewedBy = rvID
		}
	}

	if dto.CreatedFrom != nil {
		arg.CreatedFrom = pgtype.Timestamptz{Time: *dto.CreatedFrom, Valid: true}
	}

	if dto.CreatedTo != nil {
		arg.CreatedTo = pgtype.Timestamptz{Time: *dto.CreatedTo, Valid: true}
	}

	if dto.Search != "" {
		arg.SearchText = pgtype.Text{String: dto.Search, Valid: true}
	}
}

func (s *submissionService) GetSubmissionByID(ctx context.Context, id string) (*response.SubmissionResponse, *fiber.Error) {
	submissionUUID, err := convert.StringToUUID(id)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid submission ID")
	}

	submission, err := s.submissionRepo.GetByID(ctx, submissionUUID)
	if err != nil {
		return nil, fiber.NewError(fiber.StatusNotFound, "Submission not found")
	}

	return submission.ToResponse(), nil
}

func (s *submissionService) SearchSubmissions(ctx context.Context, dto *request.SearchSubmissionDto) (*response.PaginatedResponse, *fiber.Error) {
	if dto.Page < 1 {
		dto.Page = 1
	}
	if dto.Limit == 0 {
		dto.Limit = 20
	}
	offset := (dto.Page - 1) * dto.Limit

	arg := sqlc.SearchSubmissionsParams{
		Limit:  int32(dto.Limit),
		Offset: int32(offset),
	}

	s.fillSearchArgs(&arg, dto)

	var rows []*models.SubmissionEntity
	var totalRecords int64

	g, gCtx := errgroup.WithContext(ctx)

	g.Go(func() error {
		var err error
		rows, err = s.submissionRepo.Search(gCtx, arg)
		return err
	})

	g.Go(func() error {
		countArg := sqlc.CountSubmissionsParams{
			UserIds:     arg.UserIds,
			Statuses:    arg.Statuses,
			ReviewedBy:  arg.ReviewedBy,
			CreatedFrom: arg.CreatedFrom,
			CreatedTo:   arg.CreatedTo,
			SearchText:  arg.SearchText,
		}
		var err error
		totalRecords, err = s.submissionRepo.Count(gCtx, countArg)
		return err
	})

	if err := g.Wait(); err != nil {
		return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to search submissions")
	}

	submissions := models.SubmissionsEntityToResponse(rows)

	return response.BuildPaginatedResponse(submissions, totalRecords, dto.Page, dto.Limit), nil
}

func (s *submissionService) DeleteSubmission(ctx context.Context, userID string, id string, claims *response.JWTClaims) *fiber.Error {
	submissionUUID, err := convert.StringToUUID(id)
	if err != nil {
		return fiber.NewError(fiber.StatusBadRequest, "Invalid submission ID")
	}

	submission, err := s.submissionRepo.GetByID(ctx, submissionUUID)
	if err != nil {
		return fiber.NewError(fiber.StatusNotFound, "Submission not found")
	}

	shoudDelete := false
	if slices.Contains(claims.Roles, constants.RoleTypeAdmin) || slices.Contains(claims.Roles, constants.RoleTypeMod) {
		shoudDelete = true
	}

	if submission.UserID == claims.UId && submission.Status == constants.StatusTypePending {
		shoudDelete = true
	}

	if !shoudDelete {
		return fiber.NewError(fiber.StatusForbidden, "You don't have permission to delete this submission")
	}

	err = s.submissionRepo.Delete(ctx, submissionUUID)
	if err != nil {
		return fiber.NewError(fiber.StatusInternalServerError, "Failed to delete submission")
	}

	_ = s.c.Del(ctx, fmt.Sprintf("project:id:%s", submission.ProjectID))

	return nil
}
