import 'dart:async';

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

import '../models/office.dart';
import '../models/ticket.dart';
import '../models/ticket_message.dart';
import 'profile_provider.dart';
import 'supabase_provider.dart';
import 'user_offices_provider.dart';
import 'tasks_provider.dart';

final officesProvider = StreamProvider<List<Office>>((ref) {
  final client = ref.watch(supabaseClientProvider);
  return client
      .from('offices')
      .stream(primaryKey: ['id'])
      .order('name')
      .map((rows) => rows.map(Office.fromMap).toList());
});

final officesOnceProvider = FutureProvider<List<Office>>((ref) async {
  final client = ref.watch(supabaseClientProvider);
  final rows = await client.from('offices').select().order('name');
  return (rows as List<dynamic>)
      .map((row) => Office.fromMap(row as Map<String, dynamic>))
      .toList();
});

/// Office query parameters for server-side pagination.
class OfficeQuery {
  /// Creates office query parameters.
  const OfficeQuery({this.offset = 0, this.limit = 50, this.searchQuery = ''});

  /// Offset for pagination.
  final int offset;

  /// Number of items per page (default: 50).
  final int limit;

  /// Full text search query.
  final String searchQuery;

  OfficeQuery copyWith({int? offset, int? limit, String? searchQuery}) {
    return OfficeQuery(
      offset: offset ?? this.offset,
      limit: limit ?? this.limit,
      searchQuery: searchQuery ?? this.searchQuery,
    );
  }
}

final officesQueryProvider = StateProvider<OfficeQuery>(
  (ref) => const OfficeQuery(),
);

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

/// Ticket query parameters for server-side pagination and filtering.
class TicketQuery {
  /// Creates ticket query parameters.
  const TicketQuery({
    this.offset = 0,
    this.limit = 50,
    this.searchQuery = '',
    this.officeId,
    this.status,
    this.dateRange,
  });

  /// Offset for pagination.
  final int offset;

  /// Number of items per page (default: 50).
  final int limit;

  /// Full text search query.
  final String searchQuery;

  /// Filter by office ID.
  final String? officeId;

  /// Filter by status.
  final String? status;

  /// Filter by date range.
  /// Filter by date range.
  final DateTimeRange? dateRange;

  TicketQuery copyWith({
    int? offset,
    int? limit,
    String? searchQuery,
    String? officeId,
    String? status,
    DateTimeRange? dateRange,
  }) {
    return TicketQuery(
      offset: offset ?? this.offset,
      limit: limit ?? this.limit,
      searchQuery: searchQuery ?? this.searchQuery,
      officeId: officeId ?? this.officeId,
      status: status ?? this.status,
      dateRange: dateRange ?? this.dateRange,
    );
  }
}

final ticketsProvider = StreamProvider<List<Ticket>>((ref) {
  final client = ref.watch(supabaseClientProvider);
  final profileAsync = ref.watch(currentProfileProvider);
  final assignmentsAsync = ref.watch(userOfficesProvider);
  final query = ref.watch(ticketsQueryProvider);

  final profile = profileAsync.valueOrNull;
  if (profile == null) {
    return Stream.value(const <Ticket>[]);
  }

  final isGlobal =
      profile.role == 'admin' ||
      profile.role == 'dispatcher' ||
      profile.role == 'it_staff';

  // Use stream for realtime updates, then apply pagination & search filters
  // client-side because `.range(...)` is not supported on the stream builder.
  final baseStream = client
      .from('tickets')
      .stream(primaryKey: ['id'])
      .map((rows) => rows.map(Ticket.fromMap).toList());

  return baseStream.map((allTickets) {
    debugPrint('[ticketsProvider] stream event: ${allTickets.length} rows');
    var list = allTickets;

    if (!isGlobal) {
      final officeIds =
          assignmentsAsync.valueOrNull
              ?.where((assignment) => assignment.userId == profile.id)
              .map((assignment) => assignment.officeId)
              .toSet()
              .toList() ??
          <String>[];
      if (officeIds.isEmpty) return <Ticket>[];
      final allowedOffices = officeIds.toSet();
      list = list.where((t) => allowedOffices.contains(t.officeId)).toList();
    }

    if (query.officeId != null) {
      list = list.where((t) => t.officeId == query.officeId).toList();
    }
    if (query.status != null) {
      list = list.where((t) => t.status == query.status).toList();
    }
    if (query.searchQuery.isNotEmpty) {
      final q = query.searchQuery.toLowerCase();
      list = list
          .where(
            (t) =>
                t.subject.toLowerCase().contains(q) ||
                t.description.toLowerCase().contains(q),
          )
          .toList();
    }

    // Sort: newest first
    list.sort((a, b) => b.createdAt.compareTo(a.createdAt));

    // Pagination
    final start = query.offset;
    final end = (start + query.limit).clamp(0, list.length);
    if (start >= list.length) return <Ticket>[];
    return list.sublist(start, end);
  });
});

/// Provider for ticket query parameters.
final ticketsQueryProvider = StateProvider<TicketQuery>(
  (ref) => const TicketQuery(),
);

final ticketMessagesProvider =
    StreamProvider.family<List<TicketMessage>, String>((ref, ticketId) {
      final client = ref.watch(supabaseClientProvider);
      return client
          .from('ticket_messages')
          .stream(primaryKey: ['id'])
          .eq('ticket_id', ticketId)
          .order('created_at', ascending: false)
          .map((rows) => rows.map(TicketMessage.fromMap).toList());
    });

final ticketMessagesAllProvider = StreamProvider<List<TicketMessage>>((ref) {
  final client = ref.watch(supabaseClientProvider);
  return client
      .from('ticket_messages')
      .stream(primaryKey: ['id'])
      .order('created_at', ascending: false)
      .map((rows) => rows.map(TicketMessage.fromMap).toList());
});

final taskMessagesProvider = StreamProvider.family<List<TicketMessage>, String>(
  (ref, taskId) {
    final client = ref.watch(supabaseClientProvider);
    return client
        .from('ticket_messages')
        .stream(primaryKey: ['id'])
        .eq('task_id', taskId)
        .order('created_at', ascending: false)
        .map((rows) => rows.map(TicketMessage.fromMap).toList());
  },
);

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

class TicketsController {
  TicketsController(this._client);

  final SupabaseClient _client;

  Future<void> createTicket({
    required String subject,
    required String description,
    required String officeId,
  }) async {
    final actorId = _client.auth.currentUser?.id;
    final data = await _client
        .from('tickets')
        .insert({
          'subject': subject,
          'description': description,
          'office_id': officeId,
          'creator_id': _client.auth.currentUser?.id,
        })
        .select('id')
        .single();
    final ticketId = data['id'] as String?;
    if (ticketId == null) return;
    unawaited(_notifyCreated(ticketId: ticketId, actorId: actorId));
  }

  Future<void> _notifyCreated({
    required String ticketId,
    required String? actorId,
  }) async {
    try {
      final recipients = await _fetchRoleUserIds(
        roles: const ['dispatcher', 'it_staff'],
        excludeUserId: actorId,
      );
      if (recipients.isEmpty) return;
      final rows = recipients
          .map(
            (userId) => {
              'user_id': userId,
              'actor_id': actorId,
              'ticket_id': ticketId,
              'type': 'created',
            },
          )
          .toList();
      await _client.from('notifications').insert(rows);
    } catch (_) {
      return;
    }
  }

  Future<List<String>> _fetchRoleUserIds({
    required List<String> roles,
    required String? excludeUserId,
  }) async {
    try {
      final data = await _client
          .from('profiles')
          .select('id, role')
          .inFilter('role', roles);
      final rows = data as List<dynamic>;
      final ids = rows
          .map((row) => row['id'] as String?)
          .whereType<String>()
          .where((id) => id.isNotEmpty && id != excludeUserId)
          .toList();
      return ids;
    } catch (_) {
      return [];
    }
  }

  Future<TicketMessage> sendTicketMessage({
    required String ticketId,
    required String content,
  }) async {
    final data = await _client
        .from('ticket_messages')
        .insert({
          'ticket_id': ticketId,
          'content': content,
          'sender_id': _client.auth.currentUser?.id,
        })
        .select()
        .single();
    return TicketMessage.fromMap(data);
  }

  Future<TicketMessage> sendTaskMessage({
    required String taskId,
    required String? ticketId,
    required String content,
  }) async {
    final payload = <String, dynamic>{
      'task_id': taskId,
      'content': content,
      'sender_id': _client.auth.currentUser?.id,
    };
    if (ticketId != null) {
      payload['ticket_id'] = ticketId;
    }
    final data = await _client
        .from('ticket_messages')
        .insert(payload)
        .select()
        .single();
    return TicketMessage.fromMap(data);
  }

  Future<void> updateTicketStatus({
    required String ticketId,
    required String status,
  }) async {
    await _client.from('tickets').update({'status': status}).eq('id', ticketId);

    // If ticket is promoted, create a linked Task (only once) — the
    // TasksController.createTask already runs auto-assignment on creation.
    if (status == 'promoted') {
      try {
        final existing = await _client
            .from('tasks')
            .select('id')
            .eq('ticket_id', ticketId)
            .maybeSingle();
        if (existing != null) return;

        final ticketRow = await _client
            .from('tickets')
            .select('subject, description, office_id')
            .eq('id', ticketId)
            .maybeSingle();

        final title = (ticketRow?['subject'] as String?) ?? 'Task from ticket';
        final description = (ticketRow?['description'] as String?) ?? '';
        final officeId = ticketRow?['office_id'] as String?;

        final tasksCtrl = TasksController(_client);
        await tasksCtrl.createTask(
          title: title,
          description: description,
          officeId: officeId,
          ticketId: ticketId,
        );
      } catch (_) {
        // best-effort — don't fail the ticket status update
      }
    }
  }
}

class OfficesController {
  OfficesController(this._client);

  final SupabaseClient _client;

  Future<void> createOffice({required String name, String? serviceId}) async {
    final payload = {'name': name};
    if (serviceId != null) payload['service_id'] = serviceId;
    await _client.from('offices').insert(payload);
  }

  Future<void> updateOffice({
    required String id,
    required String name,
    String? serviceId,
  }) async {
    final payload = {'name': name};
    if (serviceId != null) payload['service_id'] = serviceId;
    await _client.from('offices').update(payload).eq('id', id);
  }

  Future<void> deleteOffice({required String id}) async {
    await _client.from('offices').delete().eq('id', id);
  }
}
