package service

import (
	"errors"
	"strconv"
	"strings"

	entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
	repository "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/repositories"
	validation "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phase-activities/validations"
	phaseRepo "gitlab.com/mbugroup/lti-api.git/internal/modules/master/phasess/repositories"
	"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 PhaseActivityService interface {
	GetAll(ctx *fiber.Ctx, params *validation.Query) ([]entity.PhaseActivity, int64, error)
	GetOne(ctx *fiber.Ctx, id uint) (*entity.PhaseActivity, error)
	CreateOne(ctx *fiber.Ctx, req *validation.Create) (*entity.PhaseActivity, error)
	UpdateOne(ctx *fiber.Ctx, req *validation.Update, id uint) (*entity.PhaseActivity, error)
	DeleteOne(ctx *fiber.Ctx, id uint) error
}

type phaseActivityService struct {
	Log        *logrus.Logger
	Validate   *validator.Validate
	Repository repository.PhaseActivityRepository
	PhaseRepo  phaseRepo.PhasesRepository
}

func NewPhaseActivityService(repo repository.PhaseActivityRepository, phaseRepo phaseRepo.PhasesRepository, validate *validator.Validate) PhaseActivityService {
	return &phaseActivityService{
		Log:        utils.Log,
		Validate:   validate,
		Repository: repo,
		PhaseRepo:  phaseRepo,
	}
}

func (s phaseActivityService) withRelations(db *gorm.DB) *gorm.DB {
	return db.Joins("JOIN phases ON phases.id = phase_activities.phase_id").
		Where("phases.deleted_at IS NULL")
}

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

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

	phaseActivitys, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
		db = s.withRelations(db)
		if params.Search != "" {
			db = db.Where("name ILIKE ?", "%"+params.Search+"%")
		}
		if params.PhaseIDs != "" {
			ids := parseIDs(params.PhaseIDs)
			if len(ids) > 0 {
				db = db.Where("phase_id IN ?", ids)
			}
		}
		return db.Order("created_at DESC").Order("updated_at DESC")
	})

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

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

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

	phase, err := s.PhaseRepo.GetByID(c.Context(), req.PhaseId, nil)
	if errors.Is(err, gorm.ErrRecordNotFound) {
		return nil, fiber.NewError(fiber.StatusBadRequest, "phase not found")
	}
	if err != nil {
		s.Log.Errorf("Failed to get phase: %+v", err)
		return nil, err
	}

	name := strings.TrimSpace(req.Name)
	if name == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty")
	}

	timeType := strings.TrimSpace(req.TimeType)
	if timeType == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "time_type cannot be empty")
	}

	existing, err := s.Repository.First(c.Context(), func(db *gorm.DB) *gorm.DB {
		return db.Where("phase_id = ? AND name = ? AND time_type = ?", phase.Id, name, timeType)
	})
	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
		s.Log.Errorf("Failed to check phaseActivity uniqueness: %+v", err)
		return nil, err
	}
	if existing != nil {
		return nil, fiber.NewError(fiber.StatusBadRequest, "phase activity with same name and time_type already exists")
	}

	createBody := &entity.PhaseActivity{
		PhaseId:     phase.Id,
		Name:        name,
		Description: req.Description,
		TimeType:    &timeType,
	}

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

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

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

	trimmedName := strings.TrimSpace(req.Name)
	if trimmedName == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "name cannot be empty")
	}

	trimmedTimeType := strings.TrimSpace(req.TimeType)
	if trimmedTimeType == "" {
		return nil, fiber.NewError(fiber.StatusBadRequest, "time_type cannot be empty")
	}

	updateBody := map[string]any{
		"name":      trimmedName,
		"time_type": trimmedTimeType,
	}

	if req.Description != nil {
		updateBody["description"] = *req.Description
	}

	if err := s.Repository.PatchOne(c.Context(), id, updateBody, nil); err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, fiber.NewError(fiber.StatusNotFound, "PhaseActivity not found")
		}
		s.Log.Errorf("Failed to update phaseActivity: %+v", err)
		return nil, err
	}

	return s.GetOne(c, id)
}

func (s phaseActivityService) 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, "PhaseActivity not found")
		}
		s.Log.Errorf("Failed to delete phaseActivity: %+v", err)
		return err
	}
	return nil
}

func parseIDs(raw string) []uint {
	parts := strings.Split(raw, ",")
	results := make([]uint, 0, len(parts))
	for _, part := range parts {
		value := strings.TrimSpace(part)
		if value == "" {
			continue
		}
		if n, err := strconv.ParseUint(value, 10, 64); err == nil {
			results = append(results, uint(n))
		}
	}
	return results
}
