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

// Minimal fake supabase client similar to integration test work,
// only implements the methods used by TasksController.
class _FakeClient {
  final Map<String, List<Map<String, dynamic>>> tables = {
    'tasks': [],
    'task_activity_logs': [],
  };

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

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

  _FakeQuery(this.client, this.table);

  _FakeQuery select([String? _]) => this;

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

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

  Future<Map<String, dynamic>> single() async {
    if (_insertPayload != null) {
      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);
    }
    throw Exception('unexpected single() call');
  }

  _FakeQuery update(Map<String, dynamic> payload) {
    _updatePayload = payload;
    // don't apply yet; wait for eq to know which row
    return this;
  }

  _FakeQuery eq(String field, dynamic value) {
    _eq = {field: value};
    // apply update payload now that we know which row to target
    if (_updatePayload != null) {
      final idx = client.tables[table]!.indexWhere((r) => r[field] == value);
      if (idx >= 0) {
        client.tables[table]![idx] = {
          ...client.tables[table]![idx],
          ..._updatePayload!,
        };
      }
      // clear payload after applying so subsequent eq calls don't reapply
      _updatePayload = null;
    }
    return this;
  }
}

void main() {
  group('TasksController business rules', () {
    late _FakeClient fake;
    late TasksController controller;

    setUp(() {
      fake = _FakeClient();
      controller = TasksController(fake as dynamic);
      // ignore: avoid_dynamic_calls
      // note: controller expects SupabaseClient; using dynamic bypass.
    });

    test('cannot complete a task without required metadata', () async {
      // insert a task with no metadata at all
      final row = {'id': 'tsk-1', 'status': 'queued'};
      fake.tables['tasks']!.add(row);

      expect(
        () => controller.updateTaskStatus(taskId: 'tsk-1', status: 'completed'),
        throwsA(isA<Exception>()),
      );
    });

    test('cannot complete when action taken is missing', () async {
      // insert a task that has the basic request metadata but nothing else
      final row = {
        'id': 'tsk-3',
        'status': 'queued',
        'request_type': 'Repair',
        'request_category': 'Hardware',
      };
      fake.tables['tasks']!.add(row);

      // missing actionTaken should still prevent completion even though
      // signatories are not required.
      expect(
        () => controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed'),
        throwsA(isA<Exception>()),
      );

      // add some signatories but no actionTaken yet
      await controller.updateTask(
        taskId: 'tsk-3',
        requestedBy: 'Alice',
        notedBy: 'Bob',
        receivedBy: 'Carol',
      );
      expect(
        () => controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed'),
        throwsA(isA<Exception>()),
      );

      // once action taken is provided completion should succeed even if
      // signatories remain empty (they already have values here, but the
      // previous checks show they aren't required).
      await controller.updateTask(taskId: 'tsk-3', actionTaken: '{}');
      await controller.updateTaskStatus(taskId: 'tsk-3', status: 'completed');
      expect(
        fake.tables['tasks']!.firstWhere((t) => t['id'] == 'tsk-3')['status'],
        'completed',
      );
    });

    test('completing after adding metadata succeeds', () async {
      // insert a task
      final row = {'id': 'tsk-2', 'status': 'queued'};
      fake.tables['tasks']!.add(row);

      // update metadata via updateTask including actionTaken
      await controller.updateTask(
        taskId: 'tsk-2',
        requestType: 'Repair',
        requestCategory: 'Hardware',
        actionTaken: '{}',
      );

      await controller.updateTaskStatus(taskId: 'tsk-2', status: 'completed');
      expect(
        fake.tables['tasks']!.firstWhere((t) => t['id'] == 'tsk-2')['status'],
        'completed',
      );
    });

    test('Task.hasIncompleteDetails flag works correctly', () {
      final now = DateTime.now();
      final base = Task(
        id: 'x',
        ticketId: null,
        taskNumber: null,
        title: 't',
        description: '',
        officeId: null,
        status: 'completed',
        priority: 1,
        queueOrder: null,
        createdAt: now,
        creatorId: null,
        startedAt: null,
        completedAt: now,
        // leave all optional metadata null
      );
      expect(base.hasIncompleteDetails, isTrue);

      final full = Task(
        id: 'x',
        ticketId: null,
        taskNumber: null,
        title: 't',
        description: '',
        officeId: null,
        status: 'completed',
        priority: 1,
        queueOrder: null,
        createdAt: now,
        creatorId: null,
        startedAt: null,
        completedAt: now,
        requestedBy: 'A',
        notedBy: 'B',
        receivedBy: 'C',
        requestType: 'foo',
        requestCategory: 'bar',
        actionTaken: '{}',
      );
      expect(full.hasIncompleteDetails, isFalse);
    });
  });
}
