import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:supabase_flutter/supabase_flutter.dart';

import '../models/app_settings.dart';
import '../models/duty_schedule.dart';
import '../models/swap_request.dart';
import 'profile_provider.dart';
import 'supabase_provider.dart';

final geofenceProvider = FutureProvider<GeofenceConfig?>((ref) async {
  final client = ref.watch(supabaseClientProvider);
  final data = await client
      .from('app_settings')
      .select()
      .eq('key', 'geofence')
      .maybeSingle();
  if (data == null) return null;
  final setting = AppSetting.fromMap(data);
  return GeofenceConfig.fromJson(setting.value);
});

final dutySchedulesProvider = StreamProvider<List<DutySchedule>>((ref) {
  final client = ref.watch(supabaseClientProvider);
  final profileAsync = ref.watch(currentProfileProvider);
  final profile = profileAsync.valueOrNull;
  if (profile == null) {
    return Stream.value(const <DutySchedule>[]);
  }

  final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher';
  final base = client.from('duty_schedules').stream(primaryKey: ['id']);
  if (isAdmin) {
    return base
        .order('start_time')
        .map((rows) => rows.map(DutySchedule.fromMap).toList());
  }
  return base
      .eq('user_id', profile.id)
      .order('start_time')
      .map((rows) => rows.map(DutySchedule.fromMap).toList());
});

/// Fetch duty schedules by a list of IDs (used by UI when swap requests reference
/// schedules that are not included in the current user's `dutySchedulesProvider`).
final dutySchedulesByIdsProvider =
    FutureProvider.family<List<DutySchedule>, List<String>>((ref, ids) async {
      if (ids.isEmpty) return const <DutySchedule>[];
      final client = ref.watch(supabaseClientProvider);
      final quoted = ids.map((id) => '"$id"').join(',');
      final inList = '($quoted)';
      final rows =
          await client
                  .from('duty_schedules')
                  .select()
                  .filter('id', 'in', inList)
              as List<dynamic>;
      return rows
          .map((r) => DutySchedule.fromMap(r as Map<String, dynamic>))
          .toList();
    });

/// Fetch upcoming duty schedules for a specific user (used by swap UI to
/// let the requester pick a concrete target shift owned by the recipient).
final dutySchedulesForUserProvider =
    FutureProvider.family<List<DutySchedule>, String>((ref, userId) async {
      final client = ref.watch(supabaseClientProvider);
      final nowIso = DateTime.now().toUtc().toIso8601String();
      final rows =
          await client
                  .from('duty_schedules')
                  .select()
                  .eq('user_id', userId)
                  /* exclude past schedules by ensuring the shift has not ended */
                  .gte('end_time', nowIso)
                  .order('start_time')
              as List<dynamic>;
      return rows
          .map((r) => DutySchedule.fromMap(r as Map<String, dynamic>))
          .toList();
    });

final swapRequestsProvider = StreamProvider<List<SwapRequest>>((ref) {
  final client = ref.watch(supabaseClientProvider);
  final profileAsync = ref.watch(currentProfileProvider);
  final profile = profileAsync.valueOrNull;
  if (profile == null) {
    return Stream.value(const <SwapRequest>[]);
  }

  final isAdmin = profile.role == 'admin' || profile.role == 'dispatcher';
  final base = client.from('swap_requests').stream(primaryKey: ['id']);
  if (isAdmin) {
    return base
        .order('created_at', ascending: false)
        .map((rows) => rows.map(SwapRequest.fromMap).toList());
  }
  return base
      .order('created_at', ascending: false)
      .map(
        (rows) => rows
            .where(
              (row) =>
                  row['requester_id'] == profile.id ||
                  row['recipient_id'] == profile.id,
            )
            .map(SwapRequest.fromMap)
            .toList(),
      );
});

final workforceControllerProvider = Provider<WorkforceController>((ref) {
  final client = ref.watch(supabaseClientProvider);
  return WorkforceController(client);
});

class WorkforceController {
  WorkforceController(this._client);

  final SupabaseClient _client;

  Future<void> generateSchedule({
    required DateTime startDate,
    required DateTime endDate,
  }) async {
    await _client.rpc(
      'generate_duty_schedule',
      params: {
        'start_date': _formatDate(startDate),
        'end_date': _formatDate(endDate),
      },
    );
  }

  Future<void> insertSchedules(List<Map<String, dynamic>> schedules) async {
    if (schedules.isEmpty) return;
    await _client.from('duty_schedules').insert(schedules);
  }

  Future<String?> checkIn({
    required String dutyScheduleId,
    required double lat,
    required double lng,
  }) async {
    final data = await _client.rpc(
      'duty_check_in',
      params: {'p_duty_id': dutyScheduleId, 'p_lat': lat, 'p_lng': lng},
    );
    return data as String?;
  }

  Future<String?> requestSwap({
    required String requesterScheduleId,
    required String targetScheduleId,
    required String recipientId,
  }) async {
    final data = await _client.rpc(
      'request_shift_swap',
      params: {
        'p_shift_id': requesterScheduleId,
        'p_target_shift_id': targetScheduleId,
        'p_recipient_id': recipientId,
      },
    );
    return data as String?;
  }

  Future<void> respondSwap({
    required String swapId,
    required String action,
  }) async {
    await _client.rpc(
      'respond_shift_swap',
      params: {'p_swap_id': swapId, 'p_action': action},
    );
  }

  /// Reassign the recipient of a swap request. Only admins/dispatchers are
  /// expected to call this; the DB RLS and RPCs will additionally enforce rules.
  Future<void> reassignSwap({
    required String swapId,
    required String newRecipientId,
  }) async {
    // Prefer using an RPC for server-side validation, but update directly here
    await _client
        .from('swap_requests')
        .update({
          'recipient_id': newRecipientId,
          'status': 'pending',
          'updated_at': DateTime.now().toUtc().toIso8601String(),
        })
        .eq('id', swapId);
  }

  String _formatDate(DateTime value) {
    final date = DateTime(value.year, value.month, value.day);
    final month = date.month.toString().padLeft(2, '0');
    final day = date.day.toString().padLeft(2, '0');
    return '${date.year}-$month-$day';
  }
}
