package service

import (
	"context"
	"errors"
	"strings"
	"time"

	"wucher/internal/domain/base"
	"wucher/internal/shared/pkg/appctx"
	"wucher/internal/shared/pkg/sortkey"
)

type BaseService struct {
	repo base.Repository
}

func NewBaseService(repo base.Repository) *BaseService {
	return &BaseService{repo: repo}
}

var errInvalidBaseCategoryType = errors.New("invalid base category type")

func (s *BaseService) CreateBase(ctx context.Context, row *base.Base, categoryType string) error {
	category, ok := base.NormalizeCategoryType(categoryType)
	if !ok {
		return errInvalidBaseCategoryType
	}
	if err := validateBaseCoordinates(row); err != nil {
		return err
	}
	row.DefaultStartTimeType = normalizeBaseShiftTimeType(row.DefaultStartTimeType)
	row.DefaultEndTimeType = normalizeBaseShiftTimeType(row.DefaultEndTimeType)
	if row.DefaultStartTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftStart) == "" {
		return errors.New("default_shift_start is required")
	}
	if row.DefaultEndTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftEnd) == "" {
		return errors.New("default_shift_end is required")
	}
	return s.repo.CreateBase(ctx, row, category)
}

func (s *BaseService) UpdateBase(ctx context.Context, row *base.Base, categoryType string) error {
	category, ok := base.NormalizeCategoryType(categoryType)
	if !ok {
		return errInvalidBaseCategoryType
	}
	if err := validateBaseCoordinates(row); err != nil {
		return err
	}
	row.DefaultStartTimeType = normalizeBaseShiftTimeType(row.DefaultStartTimeType)
	row.DefaultEndTimeType = normalizeBaseShiftTimeType(row.DefaultEndTimeType)
	if row.DefaultStartTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftStart) == "" {
		return errors.New("default_shift_start is required")
	}
	if row.DefaultEndTimeType == base.ShiftTimeTypeFixed && strings.TrimSpace(row.DefaultShiftEnd) == "" {
		return errors.New("default_shift_end is required")
	}
	return s.repo.UpdateBase(ctx, row, category)
}

func (s *BaseService) DeleteBase(ctx context.Context, id []byte, categoryType string) (*base.Base, error) {
	category, ok := base.NormalizeCategoryType(categoryType)
	if !ok {
		return nil, errInvalidBaseCategoryType
	}

	// fetch existing row before deleting so we can return a copy
	row, err := s.repo.GetBaseByID(ctx, id, category)
	if err != nil {
		return nil, err
	}

	if err := s.repo.DeleteBase(ctx, id, category); err != nil {
		return nil, err
	}

	// if we had a row, populate DeletedAt/DeletedBy to reflect the repository update
	if row != nil {
		now := time.Now().UTC()
		row.DeletedAt = &now
		// try to pick actor from context if available
		if actor := appctx.GetUserID(ctx); len(actor) > 0 {
			row.DeletedBy = make([]byte, len(actor))
			copy(row.DeletedBy, actor)
		}
	}

	return row, nil
}

func (s *BaseService) GetBaseByID(ctx context.Context, id []byte, categoryType string) (*base.Base, error) {
	category, ok := base.NormalizeCategoryType(categoryType)
	if !ok {
		return nil, errInvalidBaseCategoryType
	}
	return s.repo.GetBaseByID(ctx, id, category)
}

func (s *BaseService) ListBases(ctx context.Context, filter, sort string, limit, offset int, categoryType string, dul *bool) ([]base.Base, int64, error) {
	if strings.TrimSpace(categoryType) == "" {
		return s.repo.ListBases(ctx, filter, normalizeBaseSort(sort), limit, offset, "", dul)
	}
	category, ok := base.NormalizeCategoryType(categoryType)
	if !ok {
		return nil, 0, errInvalidBaseCategoryType
	}
	return s.repo.ListBases(ctx, filter, normalizeBaseSort(sort), limit, offset, category, dul)
}

func normalizeBaseSort(sort string) string {
	return normalizeSortByRules(sort,
		sortExpr(
			joinSortClauses(sortkey.ActivePositiveSortClauses("", "is_active", "sortkey", "base", false)),
			joinSortClauses(sortkey.ActivePositiveSortClauses("", "is_active", "sortkey", "base", true)),
			"sortkey",
		),
		sortField("base"),
		sortField("base_abbreviation"),
		sortField("notes", "note", "notes"),
		sortField("is_active"),
		sortField("created_at"),
		sortField("updated_at"),
	)
}

func validateBaseCoordinates(row *base.Base) error {
	if row == nil {
		return nil
	}
	if row.Latitude < -90 || row.Latitude > 90 {
		return errors.New("latitude must be between -90 and 90")
	}
	if row.Longitude < -180 || row.Longitude > 180 {
		return errors.New("longitude must be between -180 and 180")
	}
	return nil
}

func normalizeBaseShiftTimeType(raw string) string {
	switch strings.ToUpper(strings.TrimSpace(raw)) {
	case "", base.ShiftTimeTypeFixed:
		return base.ShiftTimeTypeFixed
	case base.ShiftTimeTypeBMCT:
		return base.ShiftTimeTypeBMCT
	case base.ShiftTimeTypeECET:
		return base.ShiftTimeTypeECET
	default:
		return strings.ToUpper(strings.TrimSpace(raw))
	}
}
