-- Restore server-side geofence validation in attendance_check_in and
-- attendance_check_out, with support for both polygon and circle geofences
-- and the IT service request outside-premise override.
--
-- Context: geofence validation was added in 20260316120000 (polygon only)
-- then stripped in 20260317100000 because circle geofences weren't handled,
-- causing false-rejections for offices configured with a radius. This migration
-- restores it with full polygon + circle support using PostGIS geography.
--
-- Security: without server-side validation anyone with a valid session can
-- call the RPC directly with arbitrary coordinates and bypass the geofence.

CREATE OR REPLACE FUNCTION public.attendance_check_in(
  p_duty_id    uuid,
  p_lat        double precision,
  p_lng        double precision,
  p_accuracy   double precision DEFAULT NULL,
  p_is_mocked  boolean          DEFAULT false
) RETURNS uuid
LANGUAGE plpgsql SECURITY DEFINER
AS $$
DECLARE
  v_schedule     duty_schedules%ROWTYPE;
  v_log_id       uuid;
  v_now          timestamptz := now();
  v_status       text;
  v_geofence     jsonb;
  v_in_premise   boolean := true;
  v_override     boolean := false;
  -- polygon ray-casting
  v_polygon      jsonb;
  v_point_count  int;
  v_i            int;
  v_j            int;
  v_xi           double precision;
  v_yi           double precision;
  v_xj           double precision;
  v_yj           double precision;
  v_inside       boolean := false;
  -- circle
  v_center_lat   double precision;
  v_center_lng   double precision;
  v_radius_m     double precision;
  v_dist_m       double precision;
BEGIN
  SELECT * INTO v_schedule FROM duty_schedules WHERE id = p_duty_id;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Duty schedule not found';
  END IF;
  IF v_schedule.user_id != auth.uid() THEN
    RAISE EXCEPTION 'Not your duty schedule';
  END IF;

  -- Geofence validation
  SELECT value INTO v_geofence FROM app_settings WHERE key = 'geofence';
  IF v_geofence IS NOT NULL THEN
    v_polygon := COALESCE(v_geofence->'polygon', v_geofence->'points');

    IF v_polygon IS NOT NULL AND jsonb_array_length(v_polygon) > 2 THEN
      -- Polygon: ray-casting point-in-polygon
      v_point_count := jsonb_array_length(v_polygon);
      v_j := v_point_count - 1;
      FOR v_i IN 0..(v_point_count - 1) LOOP
        v_xi := (v_polygon->v_i->>'lng')::double precision;
        v_yi := (v_polygon->v_i->>'lat')::double precision;
        v_xj := (v_polygon->v_j->>'lng')::double precision;
        v_yj := (v_polygon->v_j->>'lat')::double precision;
        IF ((v_yi > p_lat) != (v_yj > p_lat)) AND
           (p_lng < (v_xj - v_xi) * (p_lat - v_yi) / (v_yj - v_yi) + v_xi) THEN
          v_inside := NOT v_inside;
        END IF;
        v_j := v_i;
      END LOOP;
      v_in_premise := v_inside;

    ELSIF (v_geofence->>'lat') IS NOT NULL
      AND (v_geofence->>'lng') IS NOT NULL
      AND (v_geofence->>'radius_m') IS NOT NULL THEN
      -- Circle: PostGIS ST_Distance on geography gives metres directly
      v_center_lat := (v_geofence->>'lat')::double precision;
      v_center_lng := (v_geofence->>'lng')::double precision;
      v_radius_m   := (v_geofence->>'radius_m')::double precision;
      SELECT ST_Distance(
        ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326)::geography,
        ST_SetSRID(ST_MakePoint(v_center_lng, v_center_lat), 4326)::geography
      ) INTO v_dist_m;
      v_in_premise := (v_dist_m <= v_radius_m);
    END IF;
  END IF;

  -- IT service request outside-premise override
  IF NOT v_in_premise THEN
    SELECT EXISTS (
      SELECT 1
      FROM it_service_request_assignments a
      JOIN it_service_requests r ON r.id = a.request_id
      WHERE a.user_id = auth.uid()
        AND r.outside_premise_allowed
        AND r.status IN ('scheduled', 'in_progress_dry_run', 'in_progress')
    ) INTO v_override;
    IF NOT v_override THEN
      RAISE EXCEPTION 'Outside geofence';
    END IF;
  END IF;

  -- Time window check
  IF v_now < (v_schedule.start_time - interval '2 hours') THEN
    RAISE EXCEPTION 'Too early to check in (2-hour window)';
  END IF;
  IF v_now > v_schedule.end_time THEN
    RAISE EXCEPTION 'Duty has already ended';
  END IF;

  IF v_now <= v_schedule.start_time THEN
    v_status := 'arrival';
  ELSE
    v_status := 'late';
  END IF;

  INSERT INTO attendance_logs (
    user_id, duty_schedule_id, check_in_at, check_in_lat, check_in_lng,
    check_in_accuracy, check_in_is_mocked
  )
  VALUES (
    auth.uid(), p_duty_id, v_now, p_lat, p_lng,
    p_accuracy, p_is_mocked
  )
  RETURNING id INTO v_log_id;

  UPDATE duty_schedules
  SET check_in_at       = COALESCE(check_in_at, v_now),
      check_in_location = ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326)::geography,
      status            = CASE
                            WHEN check_in_at IS NULL THEN v_status::duty_status
                            ELSE status
                          END
  WHERE id = p_duty_id;

  RETURN v_log_id;
END;
$$;

CREATE OR REPLACE FUNCTION public.attendance_check_out(
  p_attendance_id uuid,
  p_lat           double precision,
  p_lng           double precision,
  p_justification text             DEFAULT NULL,
  p_accuracy      double precision DEFAULT NULL,
  p_is_mocked     boolean          DEFAULT false
) RETURNS void
LANGUAGE plpgsql SECURITY DEFINER
AS $$
DECLARE
  v_log          attendance_logs%ROWTYPE;
  v_geofence     jsonb;
  v_in_premise   boolean := true;
  v_override     boolean := false;
  v_polygon      jsonb;
  v_point_count  int;
  v_i            int;
  v_j            int;
  v_xi           double precision;
  v_yi           double precision;
  v_xj           double precision;
  v_yj           double precision;
  v_inside       boolean := false;
  v_center_lat   double precision;
  v_center_lng   double precision;
  v_radius_m     double precision;
  v_dist_m       double precision;
BEGIN
  SELECT * INTO v_log FROM attendance_logs WHERE id = p_attendance_id;
  IF NOT FOUND THEN
    RAISE EXCEPTION 'Attendance log not found';
  END IF;
  IF v_log.user_id != auth.uid() THEN
    RAISE EXCEPTION 'Not your attendance log';
  END IF;
  IF v_log.check_out_at IS NOT NULL THEN
    RAISE EXCEPTION 'Already checked out';
  END IF;

  -- Geofence validation on checkout (same logic as check-in)
  SELECT value INTO v_geofence FROM app_settings WHERE key = 'geofence';
  IF v_geofence IS NOT NULL THEN
    v_polygon := COALESCE(v_geofence->'polygon', v_geofence->'points');

    IF v_polygon IS NOT NULL AND jsonb_array_length(v_polygon) > 2 THEN
      v_point_count := jsonb_array_length(v_polygon);
      v_j := v_point_count - 1;
      FOR v_i IN 0..(v_point_count - 1) LOOP
        v_xi := (v_polygon->v_i->>'lng')::double precision;
        v_yi := (v_polygon->v_i->>'lat')::double precision;
        v_xj := (v_polygon->v_j->>'lng')::double precision;
        v_yj := (v_polygon->v_j->>'lat')::double precision;
        IF ((v_yi > p_lat) != (v_yj > p_lat)) AND
           (p_lng < (v_xj - v_xi) * (p_lat - v_yi) / (v_yj - v_yi) + v_xi) THEN
          v_inside := NOT v_inside;
        END IF;
        v_j := v_i;
      END LOOP;
      v_in_premise := v_inside;

    ELSIF (v_geofence->>'lat') IS NOT NULL
      AND (v_geofence->>'lng') IS NOT NULL
      AND (v_geofence->>'radius_m') IS NOT NULL THEN
      v_center_lat := (v_geofence->>'lat')::double precision;
      v_center_lng := (v_geofence->>'lng')::double precision;
      v_radius_m   := (v_geofence->>'radius_m')::double precision;
      SELECT ST_Distance(
        ST_SetSRID(ST_MakePoint(p_lng, p_lat), 4326)::geography,
        ST_SetSRID(ST_MakePoint(v_center_lng, v_center_lat), 4326)::geography
      ) INTO v_dist_m;
      v_in_premise := (v_dist_m <= v_radius_m);
    END IF;
  END IF;

  IF NOT v_in_premise THEN
    SELECT EXISTS (
      SELECT 1
      FROM it_service_request_assignments a
      JOIN it_service_requests r ON r.id = a.request_id
      WHERE a.user_id = auth.uid()
        AND r.outside_premise_allowed
        AND r.status IN ('scheduled', 'in_progress_dry_run', 'in_progress')
    ) INTO v_override;
    IF NOT v_override THEN
      RAISE EXCEPTION 'Outside geofence';
    END IF;
  END IF;

  UPDATE attendance_logs
  SET check_out_at        = now(),
      check_out_lat       = p_lat,
      check_out_lng       = p_lng,
      justification       = COALESCE(p_justification, justification),
      check_out_accuracy  = p_accuracy,
      check_out_is_mocked = p_is_mocked
  WHERE id = p_attendance_id;
END;
$$;
