import 'package:flutter_test/flutter_test.dart';
import 'package:tasq/providers/tasks_provider.dart';

// Lightweight in-memory fake Supabase client used only for this test.
class _FakeClient {
  final Map<String, List<Map<String, dynamic>>> tables = {
    'tickets': [],
    'tasks': [],
    'teams': [],
    'team_members': [],
    'duty_schedules': [],
    'task_assignments': [],
    'task_activity_logs': [],
    'notifications': [],
  };

  _FakeQuery from(String table) => _FakeQuery(this, table);
}

class _FakeQuery {
  final _FakeClient client;
  final String table;
  Map<String, dynamic>? _eq;
  List? _in;
  Map<String, dynamic>? _insertPayload;
  Map<String, dynamic>? _updatePayload;

  _FakeQuery(this.client, this.table);

  _FakeQuery select([String? _]) => this;
  _FakeQuery eq(String field, dynamic value) {
    _eq = {field: value};
    return this;
  }

  _FakeQuery inFilter(String field, List values) {
    _in = values;
    return this;
  }

  Future<Map<String, dynamic>?> maybeSingle() async {
    final rows = client.tables[table] ?? [];
    if (_eq != null) {
      final field = _eq!.keys.first;
      final value = _eq![field];
      Map<String, dynamic>? found;
      for (final r in rows) {
        if (r[field] == value) {
          found = Map<String, dynamic>.from(r);
          break;
        }
      }
      return found;
    }
    return rows.isEmpty ? null : Map<String, dynamic>.from(rows.first);
  }

  Future<List<Map<String, dynamic>>> execute() async {
    final rows = client.tables[table] ?? [];
    if (_in != null) {
      // return rows where the field (assume first) is in the list
      return rows
          .where((r) => _in!.contains(r.values.first))
          .map((r) => Map<String, dynamic>.from(r))
          .toList();
    }
    return rows.map((r) => Map<String, dynamic>.from(r)).toList();
  }

  _FakeQuery insert(Map<String, dynamic> payload) {
    _insertPayload = Map<String, dynamic>.from(payload);
    return this;
  }

  Future<Map<String, dynamic>> single() async {
    if (_insertPayload != null) {
      // emulate DB-generated id
      final id = 'tsk-${client.tables['tasks']!.length + 1}';
      final row = Map<String, dynamic>.from(_insertPayload!);
      row['id'] = id;
      client.tables[table]!.add(row);
      return Map<String, dynamic>.from(row);
    }
    if (_updatePayload != null && _eq != null) {
      final field = _eq!.keys.first;
      final value = _eq![field];
      final idx = client.tables[table]!.indexWhere((r) => r[field] == value);
      if (idx >= 0) {
        client.tables[table]![idx] = {
          ...client.tables[table]![idx],
          ..._updatePayload!,
        };
        return Map<String, dynamic>.from(client.tables[table]![idx]);
      }
    }
    throw Exception('single() called in unsupported context in fake');
  }

  Future<void> update(Map<String, dynamic> payload) async {
    _updatePayload = payload;
    if (_eq != null) {
      final field = _eq!.keys.first;
      final value = _eq![field];
      final idx = client.tables[table]!.indexWhere((r) => r[field] == value);
      if (idx >= 0) {
        client.tables[table]![idx] = {
          ...client.tables[table]![idx],
          ...payload,
        };
      }
    }
  }

  Future<void> delete() async {
    if (_eq != null) {
      final field = _eq!.keys.first;
      final value = _eq![field];
      client.tables[table]!.removeWhere((r) => r[field] == value);
    }
  }
}

void main() {
  test(
    'ticket promotion creates task and auto-assigns late-comer',
    () async {
      final fake = _FakeClient();

      // ticket row
      fake.tables['tickets']!.add({
        'id': 'TCK-1',
        'subject': 'Printer down',
        'description': 'Paper jam',
        'office_id': 'office-1',
      });

      // team covering office-1
      fake.tables['teams']!.add({
        'id': 'team-1',
        'office_ids': ['office-1'],
      });

      // two team members
      fake.tables['team_members']!.add({'team_id': 'team-1', 'user_id': 'u1'});
      fake.tables['team_members']!.add({'team_id': 'team-1', 'user_id': 'u2'});

      // both checked in today; u2 arrived later -> should be chosen
      fake.tables['duty_schedules']!.add({
        'user_id': 'u1',
        'check_in_at': DateTime(2026, 2, 18, 8, 0).toIso8601String(),
      });
      fake.tables['duty_schedules']!.add({
        'user_id': 'u2',
        'check_in_at': DateTime(2026, 2, 18, 9, 0).toIso8601String(),
      });

      // No existing tasks for ticket

      // We'll reuse production controllers but point them at our fake client
      // by creating minimal adapter wrappers. For this test we only need to
      // exercise selection+assignment via TasksController.createTask.

      // Create TicketsController-like flow manually (simulate promoted path):
      // 1) create task row (simulate TasksController.createTask)
      final taskPayload = {
        'title': 'Printer down',
        'description': 'Paper jam',
        'office_id': 'office-1',
        'ticket_id': 'TCK-1',
      };
      // emulate insert -> returns row with id
      final insertedTask = await fake
          .from('tasks')
          .insert(taskPayload)
          .single();
      final taskId = insertedTask['id'] as String;

      // 2) run simplified autoAssign logic (mirror of production selection)
      // Gather team ids covering office
      final teams = fake.tables['teams']!;
      final teamIds = teams
          .where((t) => (t['office_ids'] as List).contains('office-1'))
          .map((t) => t['id'] as String)
          .toList();

      final memberRows = fake.tables['team_members']!
          .where((m) => teamIds.contains(m['team_id']))
          .toList();
      final candidateIds = memberRows
          .map((r) => r['user_id'] as String)
          .toList();

      // read duty_schedules for candidates
      final onDuty = <String, DateTime>{};
      for (final s in fake.tables['duty_schedules']!) {
        final uid = s['user_id'] as String;
        if (!candidateIds.contains(uid)) continue;
        final checkIn = DateTime.parse(s['check_in_at'] as String);
        onDuty[uid] = checkIn;
      }

      // compute completedToday as 0 (no completed tasks in fake DB)
      final candidates = onDuty.entries
          .map(
            (e) => AutoAssignCandidate(
              userId: e.key,
              checkInAt: e.value,
              completedToday: 0,
            ),
          )
          .toList();

      final chosen = chooseAutoAssignCandidate(candidates);
      expect(chosen, equals('u2'), reason: 'Late-comer u2 should be chosen');

      // Insert assignment row (what _autoAssignTask would do)
      await fake.from('task_assignments').insert({
        'task_id': taskId,
        'user_id': chosen,
      }).single();

      // Assert task exists and assignment was created
      final createdTask = fake.tables['tasks']!.firstWhere(
        (t) => t['id'] == taskId,
      );
      expect(createdTask['ticket_id'], equals('TCK-1'));

      final assignment = fake.tables['task_assignments']!.firstWhere(
        (a) => a['task_id'] == taskId,
        orElse: () => {},
      );
      expect(assignment['user_id'], equals('u2'));
    },
    timeout: Timeout(Duration(seconds: 2)),
  );
}
