package hr

import (
	"context"

	"github.com/TaruLab/keluargamart-backend/internal/auth"
)

// LeaveTargetAnyPermission gates filing leave for somebody else.
//
// The key sits inside the hr.leaves.* namespace on purpose: roles that already
// hold that glob (hr_staff) gain it with no migration, and roles holding only
// the exact hr.leaves.store (employee, drivers) do not. See
// internal/auth/catalog.go.
const LeaveTargetAnyPermission = "hr.leaves.store.any"

// resolveLeaveTarget decides whose leave is being filed.
//
// Previously this was implicit and wrong: CreateLeave looked the employee up
// by the CALLER's user id and ignored employee_id entirely. The ERP sends a
// required Karyawan selection, so an admin picking an employee got a leave
// silently filed against themselves, and the UI reported success.
//
// The rule now:
//   - employee_id unset, or equal to the caller's own employee → self, allowed
//   - any other employee → requires LeaveTargetAnyPermission
//
// A caller with no employee record can still file for somebody else when they
// hold the permission, because their own row is not needed to compare against.
func (s *Service) resolveLeaveTarget(ctx context.Context, userID int64, in LeaveInput) (int64, error) {
	caller, callerErr := s.EmployeeByUser(ctx, userID)

	// No employee record of their own: self-filing is impossible, so the only
	// question is whether they may file for someone else.
	if callerErr != nil {
		if in.EmployeeID > 0 {
			if !hasLeaveTargetAny(ctx) {
				// 403, not ErrEmployeeRequired's 404. The employee they named
				// does exist and the caller may well be able to read them, so
				// "not found" would blame the wrong thing — and the same request
				// from a caller who happens to have their own employee row
				// already answers 403. Same refusal, same status.
				return 0, ErrLeaveForOther
			}
			if _, err := s.EmployeeByID(ctx, in.EmployeeID); err != nil {
				return 0, err
			}
			return in.EmployeeID, nil
		}
		return 0, ErrEmployeeRequired
	}

	// Unset, or explicitly their own row: the common self-service path.
	if in.EmployeeID <= 0 || in.EmployeeID == caller.ID {
		return caller.ID, nil
	}

	if !hasLeaveTargetAny(ctx) {
		return 0, ErrLeaveForOther
	}
	// Guard against filing for an employee that does not exist: without this
	// the insert would fail on the foreign key as an opaque 500.
	if _, err := s.EmployeeByID(ctx, in.EmployeeID); err != nil {
		return 0, err
	}
	return in.EmployeeID, nil
}

// hasLeaveTargetAny reports whether the caller may file for other employees.
// Admin and owner bypass inside Can, as everywhere else.
func hasLeaveTargetAny(ctx context.Context) bool {
	u := auth.UserFrom(ctx)
	return u != nil && u.Can(LeaveTargetAnyPermission)
}

// approveGuard blocks self-approval: the approver must not be the employee the
// leave belongs to.
//
// reviewLeave previously checked only that the leave was pending, so a user who
// filed their own leave could approve it in the same session. Combined with the
// misfiled-target bug above that made generate-and-approve a two-click path.
//
// A caller with no employee record is not blocked — an admin approving on
// behalf of the store is the normal case, and there is nothing to compare.
func (s *Service) approveGuard(ctx context.Context, leave *Leave, approverUserID int64) error {
	approver, err := s.EmployeeByUser(ctx, approverUserID)
	if err != nil {
		return nil
	}
	if approver != nil && approver.ID == leave.EmployeeID {
		return ErrLeaveSelfApproval
	}
	return nil
}