package service

import (
	"errors"
	"strings"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/validations"
	"gitlab.com/mbugroup/lti-api.git/internal/utils"

	"github.com/go-playground/validator/v10"
	"github.com/gofiber/fiber/v2"
	"github.com/sirupsen/logrus"
	"gorm.io/gorm"
)

type PhasesService interface {
	GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.Phases, int64, error)
	GetOne(ctx *fiber.Ctx, id uint) (*entity.Phases, error)
	CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.Phases, error)
	UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.Phases, error)
	DeleteOne(ctx *fiber.Ctx, id uint) error
}

type phasesService struct {
	Log        *logrus.Logger
	Validate   *validator.Validate
	Repository repository.PhasesRepository
}

func NewPhasesService(repo repository.PhasesRepository, validate *validator.Validate) PhasesService {
	return &phasesService{
		Log:        utils.Log,
		Validate:   validate,
		Repository: repo,
	}
}

func (s phasesService) withRelations(db *gorm.DB) *gorm.DB {
	return db
}

func (s phasesService) GetAll(c *fiber.Ctx, params *validation.Query) ([]entity.Phases, int64, error) {
	if err := s.Validate.Struct(params); err != nil {
		return nil, 0, err
	}

	offset := (params.Page - 1) * params.Limit

	phasess, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
		db = s.withRelations(db)
		if params.Search != "" {
			return db.Where("name ILIKE ?", "%"+params.Search+"%")
		}
		if params.Category != nil {
			db = db.Where("category = ?", *params.Category)
		}
		return db.Order("created_at DESC")
	})

	if err != nil {
		s.Log.Errorf("Failed to get phasess: %+v", err)
		return nil, 0, err
	}

	if len(phasess) > 0 {
		ids := make([]uint, 0, len(phasess))
		for _, phase := range phasess {
			ids = append(ids, phase.Id)
		}

		type activityCountRow struct {
			PhaseID uint
			Count   int64
		}

		var rows []activityCountRow
		if err := s.Repository.DB().WithContext(c.Context()).
			Table("phase_activities").
			Select("phase_id, COUNT(*) AS count").
			Where("phase_id IN ? AND deleted_at IS NULL", ids).
			Group("phase_id").
			Scan(&rows).Error; err != nil {
			s.Log.Errorf("Failed to count phase activities: %+v", err)
			return nil, 0, err
		}

		countMap := make(map[uint]int64, len(rows))
		for _, row := range rows {
			countMap[row.PhaseID] = row.Count
		}

		for i := range phasess {
			if count, ok := countMap[phasess[i].Id]; ok {
				phasess[i].ActivityCount = int(count)
			}
		}
	}
	return phasess, total, nil
}

func (s phasesService) GetOne(c *fiber.Ctx, id uint) (*entity.Phases, error) {
	phases, err := s.Repository.GetByID(c.Context(), id, s.withRelations)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Phases not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get phases by id: %+v", err)
		return nil, err
	}
	return phases, nil
}

func (s *phasesService) CreateOne(c *fiber.Ctx, req *validation.Create) (*entity.Phases, error) {
	if err := s.Validate.Struct(req); err != nil {
		return nil, err
	}

	if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
		return db.Where("LOWER(name) = ? AND category = ?", strings.ToLower(req.Name), req.Category)
	}); err == nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "phase already exists")
	} else if !errors.Is(err, gorm.ErrRecordNotFound) {
		s.Log.Errorf("Failed checking phase uniqueness: %+v", err)
		return nil, err
	}

	createBody := &entity.Phases{
		Name:     req.Name,
		Category: req.Category,
		IsActive: true,
	}

	if err := s.Repository.CreateOne(c.Context(), createBody, nil); err != nil {
		s.Log.Errorf("Failed to create phases: %+v", err)
		return nil, err
	}

	return s.GetOne(c, createBody.Id)
}

func (s phasesService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint) (*entity.Phases, error) {
	if err := s.Validate.Struct(req); err != nil {
		return nil, err
	}

	existing, err := s.Repository.GetByID(c.Context(), id, nil)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusNotFound, "Phases not found")
	}
	if err != nil {
		s.Log.Errorf("Failed get phases by id: %+v", err)
		return nil, err
	}

	updateBody := make(map[string]any)

	if req.Name != nil {
		if _, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
			return db.Where("LOWER(name) = ? AND category = ? AND id <> ?", strings.ToLower(*req.Name), existing.Category, id)
		}); err == nil {
			return nil, fiber.NewError(fiber.StatusBadRequest, "phase already exists")
		} else if !errors.Is(err, gorm.ErrRecordNotFound) {
			s.Log.Errorf("Failed checking phase uniqueness: %+v", err)
			return nil, err
		}

		updateBody["name"] = strings.TrimSpace(*req.Name)
	}
	if len(updateBody) == 0 {
		return s.GetOne(c, id)
	}

	if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
		s.Log.Errorf("Failed to update phases: %+v", err)
		return nil, err
	}

	return s.GetOne(c, id)
}

func (s phasesService) DeleteOne(c *fiber.Ctx, id uint) error {
	if err := s.Repository.DeleteOne(c.Context(), id); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return fiber.NewError(fiber.StatusNotFound, "Phases not found")
		}
		s.Log.Errorf("Failed to delete phases: %+v", err)
		return err
	}
	return nil
}
