import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:permission_handler/permission_handler.dart';

import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../models/profile.dart';
import '../../models/task.dart';
import '../../models/task_assignment.dart';
import '../../models/ticket.dart';
import '../../models/ticket_message.dart';
import '../../providers/profile_provider.dart';
import '../../providers/tasks_provider.dart';
import '../../providers/tickets_provider.dart';
import '../../widgets/responsive_body.dart';
import '../../widgets/reconnect_overlay.dart';
import '../../providers/realtime_controller.dart';
import 'package:skeletonizer/skeletonizer.dart';
import '../../theme/app_surfaces.dart';
import '../../widgets/mono_text.dart';
import '../../widgets/status_pill.dart';
import '../../utils/app_time.dart';

class DashboardMetrics {
  DashboardMetrics({
    required this.newTicketsToday,
    required this.closedToday,
    required this.openTickets,
    required this.avgResponse,
    required this.avgTriage,
    required this.longestResponse,
    required this.tasksCreatedToday,
    required this.tasksCompletedToday,
    required this.openTasks,
    required this.staffRows,
  });

  final int newTicketsToday;
  final int closedToday;
  final int openTickets;

  final Duration? avgResponse;
  final Duration? avgTriage;
  final Duration? longestResponse;

  final int tasksCreatedToday;
  final int tasksCompletedToday;
  final int openTasks;

  final List<StaffRowMetrics> staffRows;
}

class StaffRowMetrics {
  StaffRowMetrics({
    required this.userId,
    required this.name,
    required this.status,
    required this.ticketsRespondedToday,
    required this.tasksClosedToday,
  });

  final String userId;
  final String name;
  final String status;
  final int ticketsRespondedToday;
  final int tasksClosedToday;
}

final dashboardMetricsProvider = Provider<AsyncValue<DashboardMetrics>>((ref) {
  final ticketsAsync = ref.watch(ticketsProvider);
  final tasksAsync = ref.watch(tasksProvider);
  final profilesAsync = ref.watch(profilesProvider);
  final assignmentsAsync = ref.watch(taskAssignmentsProvider);
  final messagesAsync = ref.watch(ticketMessagesAllProvider);

  final asyncValues = [
    ticketsAsync,
    tasksAsync,
    profilesAsync,
    assignmentsAsync,
    messagesAsync,
  ];

  // Debug: log dependency loading/error states to diagnose full-page refreshes.
  debugPrint(
    '[dashboardMetricsProvider] recompute: '
    'tickets=${ticketsAsync.isLoading ? "loading" : "ready"} '
    'tasks=${tasksAsync.isLoading ? "loading" : "ready"} '
    'profiles=${profilesAsync.isLoading ? "loading" : "ready"} '
    'assignments=${assignmentsAsync.isLoading ? "loading" : "ready"} '
    'messages=${messagesAsync.isLoading ? "loading" : "ready"}',
  );

  if (asyncValues.any((value) => value.hasError)) {
    final errorValue = asyncValues.firstWhere((value) => value.hasError);
    final error = errorValue.error ?? 'Failed to load dashboard';
    final stack = errorValue.stackTrace ?? StackTrace.current;
    return AsyncError(error, stack);
  }

  // Avoid returning a loading state for the whole dashboard when *one*
  // dependency is temporarily loading (this caused the top linear
  // progress banner to appear during ticket→task promotion / task
  // completion). Only treat the dashboard as loading when *none* of the
  // dependencies have any data yet (initial load).
  final anyHasData = asyncValues.any((v) => v.valueOrNull != null);
  if (!anyHasData) {
    debugPrint(
      '[dashboardMetricsProvider] returning AsyncLoading (no dep data)',
    );
    return const AsyncLoading();
  }

  final tickets = ticketsAsync.valueOrNull ?? const <Ticket>[];
  final tasks = tasksAsync.valueOrNull ?? const <Task>[];
  final profiles = profilesAsync.valueOrNull ?? const <Profile>[];
  final assignments = assignmentsAsync.valueOrNull ?? const <TaskAssignment>[];
  final messages = messagesAsync.valueOrNull ?? const <TicketMessage>[];

  final now = AppTime.now();
  final startOfDay = DateTime(now.year, now.month, now.day);

  final staffProfiles = profiles
      .where((profile) => profile.role == 'it_staff')
      .toList();
  final staffIds = profiles
      .where(
        (profile) =>
            profile.role == 'admin' ||
            profile.role == 'dispatcher' ||
            profile.role == 'it_staff',
      )
      .map((profile) => profile.id)
      .toSet();

  bool isToday(DateTime value) => !value.isBefore(startOfDay);

  final firstStaffMessageByTicket = <String, DateTime>{};
  final lastStaffMessageByUser = <String, DateTime>{};
  final respondedTicketsByUser = <String, Set<String>>{};
  for (final message in messages) {
    final ticketId = message.ticketId;
    final senderId = message.senderId;
    if (ticketId != null && senderId != null && staffIds.contains(senderId)) {
      final current = firstStaffMessageByTicket[ticketId];
      if (current == null || message.createdAt.isBefore(current)) {
        firstStaffMessageByTicket[ticketId] = message.createdAt;
      }
      final last = lastStaffMessageByUser[senderId];
      if (last == null || message.createdAt.isAfter(last)) {
        lastStaffMessageByUser[senderId] = message.createdAt;
      }
      if (isToday(message.createdAt)) {
        respondedTicketsByUser
            .putIfAbsent(senderId, () => <String>{})
            .add(ticketId);
      }
    }
  }

  DateTime? respondedAtForTicket(Ticket ticket) {
    final staffMessageAt = firstStaffMessageByTicket[ticket.id];
    if (staffMessageAt != null) {
      return staffMessageAt;
    }
    if (ticket.promotedAt != null) {
      return ticket.promotedAt;
    }
    return null;
  }

  Duration? responseDuration(Ticket ticket) {
    final respondedAt = respondedAtForTicket(ticket);
    if (respondedAt == null) {
      return null;
    }
    final duration = respondedAt.difference(ticket.createdAt);
    return duration.isNegative ? Duration.zero : duration;
  }

  Duration? triageDuration(Ticket ticket) {
    final respondedAt = respondedAtForTicket(ticket);
    if (respondedAt == null) {
      return null;
    }
    final triageEnd = _earliestDate(ticket.promotedAt, ticket.closedAt);
    if (triageEnd == null) {
      return null;
    }
    final duration = triageEnd.difference(respondedAt);
    return duration.isNegative ? Duration.zero : duration;
  }

  final ticketsToday = tickets.where((ticket) => isToday(ticket.createdAt));
  final closedToday = tickets.where(
    (ticket) => ticket.closedAt != null && isToday(ticket.closedAt!),
  );
  final openTickets = tickets.where((ticket) => ticket.status != 'closed');

  final responseDurationsToday = ticketsToday
      .map(responseDuration)
      .whereType<Duration>()
      .toList();
  final triageDurationsToday = ticketsToday
      .map(triageDuration)
      .whereType<Duration>()
      .toList();

  final avgResponse = _averageDuration(responseDurationsToday);
  final avgTriage = _averageDuration(triageDurationsToday);
  final longestResponse = responseDurationsToday.isEmpty
      ? null
      : responseDurationsToday.reduce(
          (a, b) => a.inSeconds >= b.inSeconds ? a : b,
        );

  // Exclude cancelled tasks from dashboard metrics
  final tasksCreatedToday = tasks.where(
    (task) => isToday(task.createdAt) && task.status != 'cancelled',
  );
  final tasksCompletedToday = tasks.where(
    (task) =>
        task.completedAt != null &&
        isToday(task.completedAt!) &&
        task.status != 'cancelled',
  );
  final openTasks = tasks.where(
    (task) => task.status != 'completed' && task.status != 'cancelled',
  );

  final taskById = {for (final task in tasks) task.id: task};
  final staffOnTask = <String>{};
  for (final assignment in assignments) {
    final task = taskById[assignment.taskId];
    if (task == null) {
      continue;
    }
    if (task.status == 'in_progress') {
      staffOnTask.add(assignment.userId);
    }
  }

  final tasksClosedByUser = <String, Set<String>>{};
  for (final assignment in assignments) {
    final task = taskById[assignment.taskId];
    if (task == null || task.completedAt == null) {
      continue;
    }
    if (!isToday(task.completedAt!)) {
      continue;
    }
    tasksClosedByUser
        .putIfAbsent(assignment.userId, () => <String>{})
        .add(task.id);
  }

  const triageWindow = Duration(minutes: 1);
  final triageCutoff = now.subtract(triageWindow);

  var staffRows = staffProfiles.map((staff) {
    final lastMessage = lastStaffMessageByUser[staff.id];
    final ticketsResponded = respondedTicketsByUser[staff.id]?.length ?? 0;
    final tasksClosed = tasksClosedByUser[staff.id]?.length ?? 0;
    final onTask = staffOnTask.contains(staff.id);
    final inTriage = lastMessage != null && lastMessage.isAfter(triageCutoff);
    final status = onTask
        ? 'On task'
        : inTriage
        ? 'In triage'
        : 'Vacant';

    return StaffRowMetrics(
      userId: staff.id,
      name: staff.fullName.isNotEmpty ? staff.fullName : staff.id,
      status: status,
      ticketsRespondedToday: ticketsResponded,
      tasksClosedToday: tasksClosed,
    );
  }).toList();

  // Order IT staff by combined activity (tickets responded today + tasks closed today)
  // descending so most-active staff appear first. Use name as a stable tiebreaker.
  staffRows.sort((a, b) {
    final aCount = a.ticketsRespondedToday + a.tasksClosedToday;
    final bCount = b.ticketsRespondedToday + b.tasksClosedToday;
    if (bCount != aCount) return bCount.compareTo(aCount);
    return a.name.compareTo(b.name);
  });

  return AsyncData(
    DashboardMetrics(
      newTicketsToday: ticketsToday.length,
      closedToday: closedToday.length,
      openTickets: openTickets.length,
      avgResponse: avgResponse,
      avgTriage: avgTriage,
      longestResponse: longestResponse,
      tasksCreatedToday: tasksCreatedToday.length,
      tasksCompletedToday: tasksCompletedToday.length,
      openTasks: openTasks.length,
      staffRows: staffRows,
    ),
  );
});

class DashboardScreen extends StatefulWidget {
  const DashboardScreen({super.key});

  @override
  State<DashboardScreen> createState() => _DashboardScreenState();
}

class _DashboardScreenState extends State<DashboardScreen> {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) async {
      final prefs = await SharedPreferences.getInstance();
      final seen = prefs.getBool('has_seen_notif_showcase') ?? false;
      if (!seen) {
        if (!mounted) return;
        await showDialog<void>(
          context: context,
          builder: (context) => AlertDialog(
            title: const Text('Never miss an update'),
            content: const Text(
              'Ensure notification sounds and vibration are enabled for important alerts.',
            ),
            actions: [
              TextButton(
                onPressed: () {
                  Navigator.of(context).pop();
                  openAppSettings();
                },
                child: const Text('Open settings'),
              ),
              TextButton(
                onPressed: () => Navigator.of(context).pop(),
                child: const Text('Got it'),
              ),
            ],
          ),
        );
        await prefs.setBool('has_seen_notif_showcase', true);
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    final realtime = ProviderScope.containerOf(
      context,
    ).read(realtimeControllerProvider);

    return ResponsiveBody(
      child: Skeletonizer(
        enabled: realtime.isAnyStreamRecovering,
        child: LayoutBuilder(
          builder: (context, constraints) {
            final sections = <Widget>[
              const SizedBox(height: 16),
              _sectionTitle(context, 'IT Staff Pulse'),
              const _StaffTable(),
              const SizedBox(height: 20),
              _sectionTitle(context, 'Core Daily KPIs'),
              _cardGrid(context, [
                _MetricCard(
                  title: 'New tickets today',
                  valueBuilder: (metrics) => metrics.newTicketsToday.toString(),
                ),
                _MetricCard(
                  title: 'Closed today',
                  valueBuilder: (metrics) => metrics.closedToday.toString(),
                ),
                _MetricCard(
                  title: 'Open tickets',
                  valueBuilder: (metrics) => metrics.openTickets.toString(),
                ),
              ]),
              const SizedBox(height: 20),
              _sectionTitle(context, 'Task Flow'),
              _cardGrid(context, [
                _MetricCard(
                  title: 'Tasks created',
                  valueBuilder: (metrics) =>
                      metrics.tasksCreatedToday.toString(),
                ),
                _MetricCard(
                  title: 'Tasks completed',
                  valueBuilder: (metrics) =>
                      metrics.tasksCompletedToday.toString(),
                ),
                _MetricCard(
                  title: 'Open tasks',
                  valueBuilder: (metrics) => metrics.openTasks.toString(),
                ),
              ]),
              const SizedBox(height: 20),
              _sectionTitle(context, 'TAT / Response'),
              _cardGrid(context, [
                _MetricCard(
                  title: 'Avg response',
                  valueBuilder: (metrics) =>
                      _formatDuration(metrics.avgResponse),
                ),
                _MetricCard(
                  title: 'Avg triage',
                  valueBuilder: (metrics) => _formatDuration(metrics.avgTriage),
                ),
                _MetricCard(
                  title: 'Longest response',
                  valueBuilder: (metrics) =>
                      _formatDuration(metrics.longestResponse),
                ),
              ]),
            ];

            final content = Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: const EdgeInsets.only(top: 16, bottom: 8),
                  child: Align(
                    alignment: Alignment.center,
                    child: Text(
                      'Dashboard',
                      textAlign: TextAlign.center,
                      style: Theme.of(context).textTheme.titleLarge?.copyWith(
                        fontWeight: FontWeight.w700,
                      ),
                    ),
                  ),
                ),
                const _DashboardStatusBanner(),
                ...sections,
              ],
            );

            return Stack(
              children: [
                SingleChildScrollView(
                  padding: const EdgeInsets.only(bottom: 24),
                  child: Center(
                    child: ConstrainedBox(
                      constraints: BoxConstraints(
                        minHeight: constraints.maxHeight,
                      ),
                      child: content,
                    ),
                  ),
                ),
                if (realtime.isAnyStreamRecovering)
                  const Positioned(
                    bottom: 16,
                    right: 16,
                    child: ReconnectIndicator(),
                  ),
              ],
            );
          },
        ),
      ),
    );
  }

  Widget _sectionTitle(BuildContext context, String title) {
    return Padding(
      padding: const EdgeInsets.only(bottom: 12),
      child: Text(
        title,
        style: Theme.of(
          context,
        ).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
      ),
    );
  }

  Widget _cardGrid(BuildContext context, List<Widget> cards) {
    return LayoutBuilder(
      builder: (context, constraints) {
        final width = constraints.maxWidth;
        if (width < 520) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              for (var i = 0; i < cards.length; i++) ...[
                cards[i],
                if (i < cards.length - 1) const SizedBox(height: 12),
              ],
            ],
          );
        }
        final spacing = 12.0;
        final minCardWidth = 220.0;
        final totalWidth =
            cards.length * minCardWidth + spacing * (cards.length - 1);
        final fits = totalWidth <= width;
        final cardWidth = fits
            ? (width - spacing * (cards.length - 1)) / cards.length
            : minCardWidth;

        final row = Row(
          children: [
            for (var i = 0; i < cards.length; i++) ...[
              SizedBox(width: cardWidth, child: cards[i]),
              if (i < cards.length - 1) const SizedBox(width: 12),
            ],
          ],
        );

        if (fits) {
          return row;
        }

        return SingleChildScrollView(
          scrollDirection: Axis.horizontal,
          child: SizedBox(width: totalWidth, child: row),
        );
      },
    );
  }
}

class _DashboardStatusBanner extends ConsumerWidget {
  const _DashboardStatusBanner();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Watch a small derived string state so only the banner rebuilds when
    // its visibility/content actually changes.
    final bannerState = ref.watch(
      dashboardMetricsProvider.select(
        (av) => av.when<String>(
          data: (_) => 'data',
          loading: () => 'loading',
          error: (e, _) => 'error:${e.toString()}',
        ),
      ),
    );

    if (bannerState == 'loading') {
      return const Padding(
        padding: EdgeInsets.only(bottom: 12),
        child: LinearProgressIndicator(minHeight: 2),
      );
    }

    if (bannerState.startsWith('error:')) {
      final errorText = bannerState.substring(6);
      return Padding(
        padding: const EdgeInsets.only(bottom: 12),
        child: Text(
          'Dashboard data error: $errorText',
          style: Theme.of(context).textTheme.bodySmall?.copyWith(
            color: Theme.of(context).colorScheme.error,
          ),
        ),
      );
    }

    return const SizedBox.shrink();
  }
}

class _MetricCard extends ConsumerWidget {
  const _MetricCard({required this.title, required this.valueBuilder});

  final String title;
  final String Function(DashboardMetrics metrics) valueBuilder;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Only watch the single string value for this card so unrelated metric
    // updates don't rebuild the whole card. This makes updates feel much
    // smoother and avoids full-page refreshes.
    final value = ref.watch(
      dashboardMetricsProvider.select(
        (av) => av.when<String>(
          data: (m) => valueBuilder(m),
          loading: () => '—',
          error: (error, _) => 'Error',
        ),
      ),
    );

    return AnimatedContainer(
      duration: const Duration(milliseconds: 220),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).colorScheme.surface,
        borderRadius: BorderRadius.circular(20),
        border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: Theme.of(
              context,
            ).textTheme.labelLarge?.copyWith(fontWeight: FontWeight.w600),
          ),
          const SizedBox(height: 10),

          // Animate only the metric text (not the whole card) for a
          // subtle, smooth update.
          AnimatedSwitcher(
            duration: const Duration(milliseconds: 220),
            transitionBuilder: (child, anim) =>
                FadeTransition(opacity: anim, child: child),
            child: MonoText(
              value,
              key: ValueKey(value),
              style: Theme.of(
                context,
              ).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.w700),
            ),
          ),
        ],
      ),
    );
  }
}

class _StaffTable extends StatelessWidget {
  const _StaffTable();

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Theme.of(context).colorScheme.surface,
        borderRadius: BorderRadius.circular(AppSurfaces.of(context).cardRadius),
        border: Border.all(color: Theme.of(context).colorScheme.outlineVariant),
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: const [
          _StaffTableHeader(),
          SizedBox(height: 8),
          _StaffTableBody(),
        ],
      ),
    );
  }
}

class _StaffTableHeader extends StatelessWidget {
  const _StaffTableHeader();

  @override
  Widget build(BuildContext context) {
    final style = Theme.of(
      context,
    ).textTheme.labelMedium?.copyWith(fontWeight: FontWeight.w700);
    return Row(
      children: [
        Expanded(flex: 3, child: Text('IT Staff', style: style)),
        Expanded(flex: 2, child: Text('Status', style: style)),
        Expanded(flex: 2, child: Text('Tickets', style: style)),
        Expanded(flex: 2, child: Text('Tasks', style: style)),
      ],
    );
  }
}

class _StaffTableBody extends ConsumerWidget {
  const _StaffTableBody();

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    // Only listen to the staff rows and the overall provider state to keep
    // rebuilds scoped to this small area.
    final providerState = ref.watch(
      dashboardMetricsProvider.select(
        (av) => av.when<String>(
          data: (_) => 'data',
          loading: () => 'loading',
          error: (e, _) => 'error:${e.toString()}',
        ),
      ),
    );

    final staffRows = ref.watch(
      dashboardMetricsProvider.select(
        (av) => av.when<List<StaffRowMetrics>>(
          data: (m) => m.staffRows,
          loading: () => const <StaffRowMetrics>[],
          error: (error, _) => const <StaffRowMetrics>[],
        ),
      ),
    );

    if (providerState == 'loading') {
      return const Text('Loading staff...');
    }

    if (providerState.startsWith('error:')) {
      final err = providerState.substring(6);
      return Text('Failed to load staff: $err');
    }

    if (staffRows.isEmpty) {
      return Text(
        'No IT staff available.',
        style: Theme.of(context).textTheme.bodySmall,
      );
    }

    return Column(
      children: staffRows.map((row) => _StaffRow(row: row)).toList(),
    );
  }
}

class _StaffRow extends StatelessWidget {
  const _StaffRow({required this.row});

  final StaffRowMetrics row;

  @override
  Widget build(BuildContext context) {
    final valueStyle = Theme.of(context).textTheme.bodySmall;
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 6),
      child: Row(
        children: [
          Expanded(flex: 3, child: Text(row.name, style: valueStyle)),
          Expanded(
            flex: 2,
            child: Align(
              alignment: Alignment.centerLeft,
              child: StatusPill(label: row.status),
            ),
          ),
          Expanded(
            flex: 2,
            child: Text(
              row.ticketsRespondedToday.toString(),
              style: valueStyle,
            ),
          ),
          Expanded(
            flex: 2,
            child: Text(row.tasksClosedToday.toString(), style: valueStyle),
          ),
        ],
      ),
    );
  }
}

Duration? _averageDuration(List<Duration> durations) {
  if (durations.isEmpty) {
    return null;
  }
  final totalSeconds = durations
      .map((duration) => duration.inSeconds)
      .reduce((a, b) => a + b);
  return Duration(seconds: (totalSeconds / durations.length).round());
}

DateTime? _earliestDate(DateTime? first, DateTime? second) {
  if (first == null) return second;
  if (second == null) return first;
  return first.isBefore(second) ? first : second;
}

String _formatDuration(Duration? duration) {
  if (duration == null) {
    return 'Pending';
  }
  if (duration.inSeconds < 60) {
    return 'Less than a minute';
  }
  final hours = duration.inHours;
  final minutes = duration.inMinutes.remainder(60);
  if (hours > 0) {
    return '${hours}h ${minutes}m';
  }
  return '${minutes}m';
}
