import 'dart:math' as math;

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:intl/intl.dart';

import '../../../core/api/api_error_mapper.dart';
import '../../../core/l10n/l10n.dart';
import '../../../core/ui/async_state_views.dart';
import '../../auth/data/auth_providers.dart';
import '../../recipes/data/recipe_providers.dart';
import '../../recipes/domain/recipe.dart';
import '../data/meal_plan_providers.dart';
import '../domain/inventory_compare_item.dart';
import '../domain/meal_plan_dashboard.dart';
import '../domain/meal_plan_entry.dart';
import '../domain/meal_plan_week.dart';
import '../domain/shopping_item.dart';

class MealPlanScreen extends ConsumerStatefulWidget {
  const MealPlanScreen({super.key});

  @override
  ConsumerState<MealPlanScreen> createState() => _MealPlanScreenState();
}

class _MealPlanScreenState extends ConsumerState<MealPlanScreen> {
  String? _savingDate;

  Future<void> _saveSelection({
    required String date,
    required int? recipeId,
    int? servings,
  }) async {
    if (_savingDate != null) return;

    setState(() => _savingDate = date);
    try {
      final token = await ref.read(authStateProvider.future);
      final repository = ref.read(mealPlanRepositoryProvider);

      if (recipeId == null) {
        await repository.deleteByDate(date, token: token);
      } else {
        await repository.upsert(
          date: date,
          recipeId: recipeId,
          servings: servings,
          token: token,
        );
      }

      ref.invalidate(mealPlanDashboardProvider);
    } catch (error) {
      if (!mounted) return;
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text(mapErrorToUserMessage(error, context))),
      );
    } finally {
      if (mounted) {
        setState(() => _savingDate = null);
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    final l10n = context.l10n;
    final locale = Localizations.localeOf(context).toLanguageTag();
    final recipesAsync = ref.watch(recipesProvider);
    final dashboardAsync = ref.watch(mealPlanDashboardProvider);
    final week = ref.watch(mealPlanWeekProvider);

    if (recipesAsync.isLoading || dashboardAsync.isLoading) {
      return LoadingStateView(label: l10n.mealPlanLoading);
    }

    if (recipesAsync.hasError || dashboardAsync.hasError) {
      final error = recipesAsync.error ?? dashboardAsync.error;
      return ErrorStateView(
        message: mapErrorToUserMessage(error ?? l10n.unexpectedError, context),
        onRetry: () {
          ref.invalidate(recipesProvider);
          ref.invalidate(mealPlanDashboardProvider);
        },
      );
    }

    final recipes = recipesAsync.maybeWhen(data: (d) => d, orElse: () => null) ?? const <Recipe>[];
    final dashboard = dashboardAsync.maybeWhen(data: (d) => d, orElse: () => null) ??
        const MealPlanDashboard(
          entries: [],
          shoppingItems: [],
          inventoryCompareItems: [],
        );

    if (recipes.isEmpty) {
      return EmptyStateView(
        title: l10n.mealPlanNoRecipesTitle,
        description: l10n.mealPlanNoRecipesDescription,
      );
    }

    final plannedCount = week.days.where((day) => dashboard.entryForDate(day) != null).length;
    final weekLabel = _formatWeekLabel(week, locale);

    return ListView(
      padding: const EdgeInsets.all(12),
      children: [
        Wrap(
          spacing: 8,
          runSpacing: 8,
          crossAxisAlignment: WrapCrossAlignment.center,
          children: [
            OutlinedButton.icon(
              onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).decrement(),
              icon: const Icon(Icons.chevron_left),
              label: Text(l10n.mealPlanWeekPrevious),
            ),
            Chip(label: Text(weekLabel)),
            OutlinedButton.icon(
              onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).increment(),
              icon: const Icon(Icons.chevron_right),
              label: Text(l10n.mealPlanWeekNext),
            ),
            if (ref.watch(mealPlanWeekOffsetProvider) != 0)
              TextButton(
                onPressed: () => ref.read(mealPlanWeekOffsetProvider.notifier).reset(),
                child: Text(l10n.mealPlanWeekCurrent),
              ),
            IconButton(
              tooltip: 'Gå till recept',
              icon: const Icon(Icons.restaurant_menu),
              onPressed: () => context.go('/recipes'),
            ),
          ],
        ),
        const SizedBox(height: 12),
        ...week.days.map(
          (day) => _DayCard(
            date: day,
            locale: locale,
            entry: dashboard.entryForDate(day),
            recipes: recipes,
            isSaving: _savingDate == week.isoDate(day),
            onSelected: (recipeId, servings) => _saveSelection(
              date: week.isoDate(day),
              recipeId: recipeId,
              servings: servings,
            ),
          ),
        ),
        const SizedBox(height: 16),
        _ShoppingSection(
          dashboard: dashboard,
          plannedCount: plannedCount,
        ),
      ],
    );
  }

  String _formatWeekLabel(MealPlanWeek week, String locale) {
    final from = DateFormat('d MMM', locale).format(week.start);
    final to = DateFormat('d MMM y', locale).format(week.end);
    return '$from - $to';
  }
}

class _DayCard extends StatelessWidget {
  final DateTime date;
  final String locale;
  final MealPlanEntry? entry;
  final List<Recipe> recipes;
  final bool isSaving;
  final void Function(int? recipeId, int? servings) onSelected;

  const _DayCard({
    required this.date,
    required this.locale,
    required this.entry,
    required this.recipes,
    required this.isSaving,
    required this.onSelected,
  });

  @override
  Widget build(BuildContext context) {
    final l10n = context.l10n;
    final theme = Theme.of(context);
    final today = DateTime.now();
    final isToday = today.year == date.year && today.month == date.month && today.day == date.day;
    final selectedValue = entry?.recipe.id.toString() ?? '';
    final recipeServings = entry?.recipe.servings;
    final currentServings = entry?.servings ?? recipeServings;

    return Card(
      margin: const EdgeInsets.only(bottom: 10),
      color: isToday ? theme.colorScheme.primaryContainer.withValues(alpha: 0.45) : null,
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              _formatDayLabel(),
              style: theme.textTheme.titleMedium,
            ),
            const SizedBox(height: 12),
            DropdownButtonFormField<String>(
              initialValue: selectedValue,
              isExpanded: true,
              decoration: InputDecoration(
                labelText: l10n.mealPlanSelectRecipe,
                border: const OutlineInputBorder(),
              ),
              items: [
                DropdownMenuItem<String>(
                  value: '',
                  child: Text(l10n.mealPlanDayNoRecipe),
                ),
                ...recipes
                    .map(
                      (recipe) => DropdownMenuItem<String>(
                        value: recipe.id.toString(),
                        child: Text(recipe.title),
                      ),
                    )
                    .toList(),
              ],
              onChanged: isSaving
                  ? null
                  : (value) {
                      onSelected(
                        int.tryParse((value ?? '').trim()),
                        entry?.servings,
                      );
                    },
            ),
            const SizedBox(height: 8),
            Wrap(
              spacing: 8,
              runSpacing: 8,
              crossAxisAlignment: WrapCrossAlignment.center,
              children: [
                if (entry != null)
                  TextButton.icon(
                    onPressed: isSaving ? null : () => context.push('/recipes/${entry!.recipe.id}'),
                    icon: const Icon(Icons.open_in_new),
                    label: Text(l10n.mealPlanViewRecipe),
                  ),
                if (recipeServings != null && entry != null)
                  SizedBox(
                    width: 220,
                    child: DropdownButtonFormField<int>(
                      initialValue: currentServings,
                      decoration: InputDecoration(
                        labelText: l10n.mealPlanServingsLabel,
                        border: const OutlineInputBorder(),
                      ),
                      items: _servingsOptions(currentServings, recipeServings)
                          .map(
                            (servings) => DropdownMenuItem<int>(
                              value: servings,
                              child: Text(servings.toString()),
                            ),
                          )
                          .toList(),
                      onChanged: isSaving
                          ? null
                          : (value) {
                              if (value == null || entry == null) return;
                              onSelected(entry!.recipe.id, value);
                            },
                    ),
                  ),
                if (entry != null && entry!.servings != null && recipeServings != null)
                  OutlinedButton(
                    onPressed: isSaving ? null : () => onSelected(entry!.recipe.id, null),
                    child: Text('${l10n.mealPlanResetServings} (${recipeServings.toString()})'),
                  ),
                if (isSaving)
                  Text(
                    l10n.mealPlanSaving,
                    style: theme.textTheme.bodySmall,
                  ),
              ],
            ),
          ],
        ),
      ),
    );
  }

  String _formatDayLabel() {
    final weekday = DateFormat('EEEE', locale).format(date);
    final monthDay = DateFormat('d MMM', locale).format(date);
    return '${_capitalize(weekday)} - $monthDay';
  }

  List<int> _servingsOptions(int? currentServings, int recipeServings) {
    final upperBound = math.max(12, math.max(currentServings ?? recipeServings, recipeServings));
    return List.generate(upperBound, (index) => index + 1);
  }

  String _capitalize(String value) {
    if (value.isEmpty) return value;
    return '${value[0].toUpperCase()}${value.substring(1)}';
  }
}

class _ShoppingSection extends StatelessWidget {
  final MealPlanDashboard dashboard;
  final int plannedCount;

  const _ShoppingSection({
    required this.dashboard,
    required this.plannedCount,
  });

  @override
  Widget build(BuildContext context) {
    final l10n = context.l10n;
    final theme = Theme.of(context);

    return Card(
      child: Padding(
        padding: const EdgeInsets.all(12),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(l10n.mealPlanShoppingTitle, style: theme.textTheme.titleMedium),
            const SizedBox(height: 4),
            Text(
              l10n.mealPlanPlannedRecipes(plannedCount),
              style: theme.textTheme.bodySmall,
            ),
            const SizedBox(height: 12),
            if (plannedCount == 0)
              Text(l10n.mealPlanPickRecipeHint)
            else ...[
              _ShoppingSummary(compareItems: dashboard.inventoryCompareItems),
              const SizedBox(height: 8),
              if (dashboard.shoppingItems.isEmpty)
                Text(l10n.mealPlanNoShoppingItems)
              else
                ..._buildRows(context),
            ],
          ],
        ),
      ),
    );
  }

  List<Widget> _buildRows(BuildContext context) {
    final items = dashboard.shoppingItems
        .map((item) => _EnrichedShoppingItem.from(item, dashboard.inventoryCompareItems))
        .toList()
      ..sort((a, b) {
        final statusDiff = a.sortOrder.compareTo(b.sortOrder);
        if (statusDiff != 0) return statusDiff;
        return a.item.name.toLowerCase().compareTo(b.item.name.toLowerCase());
      });

    return items
        .map(
          (item) => Padding(
            padding: const EdgeInsets.only(bottom: 8),
            child: _ShoppingRow(item: item),
          ),
        )
        .toList();
  }
}

class _ShoppingSummary extends StatelessWidget {
  final List<InventoryCompareItem> compareItems;

  const _ShoppingSummary({required this.compareItems});

  @override
  Widget build(BuildContext context) {
    if (compareItems.isEmpty) {
      return const SizedBox.shrink();
    }

    final l10n = context.l10n;
    final missing = compareItems.where((item) => item.status == 'missing').length;
    final enough = compareItems.where((item) => item.status == 'enough').length;
    final pantry = compareItems.where((item) => item.status == 'pantry').length;
    final partial = compareItems
        .where((item) => item.status == 'missing' && item.availableQuantity > 0)
        .length;

    final adjustedMissing = missing - partial;

    return Wrap(
      spacing: 8,
      runSpacing: 8,
      children: [
        if (adjustedMissing > 0)
          Chip(
            avatar: const Icon(Icons.error_outline, size: 18),
            label: Text(l10n.mealPlanMissingCount(adjustedMissing)),
          ),
        if (partial > 0)
          Chip(
            avatar: const Icon(Icons.warning_amber_rounded, size: 18),
            label: Text(l10n.mealPlanPartialCount(partial)),
          ),
        if (enough > 0)
          Chip(
            avatar: const Icon(Icons.check_circle_outline, size: 18),
            label: Text(l10n.mealPlanEnoughCount(enough)),
          ),
        if (pantry > 0)
          Chip(
            avatar: const Icon(Icons.inventory_2_outlined, size: 18),
            label: Text(l10n.mealPlanPantryCount(pantry)),
          ),
        if (adjustedMissing <= 0 && partial <= 0)
          Chip(
            avatar: const Icon(Icons.check_circle_outline, size: 18),
            label: Text(l10n.mealPlanAllAtHome),
          ),
      ],
    );
  }
}

class _ShoppingRow extends StatelessWidget {
  final _EnrichedShoppingItem item;

  const _ShoppingRow({required this.item});

  @override
  Widget build(BuildContext context) {
    final l10n = context.l10n;
    final theme = Theme.of(context);
    final colorScheme = theme.colorScheme;

    final (icon, label, background) = switch (item.status) {
      _DisplayStatus.partial => (
          Icons.warning_amber_rounded,
          l10n.mealPlanStatusPartial,
          colorScheme.tertiaryContainer,
        ),
      _DisplayStatus.enough => (
          Icons.check_circle_outline,
          l10n.mealPlanStatusEnough,
          colorScheme.secondaryContainer,
        ),
      _DisplayStatus.pantry => (
          Icons.inventory_2_outlined,
          l10n.mealPlanStatusPantry,
          colorScheme.surfaceContainerHighest,
        ),
      _DisplayStatus.missing => (
          Icons.error_outline,
          l10n.mealPlanStatusMissing,
          colorScheme.errorContainer,
        ),
    };

    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10),
      decoration: BoxDecoration(
        color: background,
        borderRadius: BorderRadius.circular(12),
      ),
      child: Row(
        children: [
          Icon(icon, size: 18),
          const SizedBox(width: 10),
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(item.item.name, style: theme.textTheme.bodyLarge),
                const SizedBox(height: 2),
                Text(
                  item.subtitle(label),
                  style: theme.textTheme.bodySmall,
                ),
              ],
            ),
          ),
          const SizedBox(width: 8),
          Text(
            item.trailingText,
            style: theme.textTheme.labelLarge,
          ),
        ],
      ),
    );
  }
}

enum _DisplayStatus { missing, partial, enough, pantry }

class _EnrichedShoppingItem {
  final ShoppingItem item;
  final InventoryCompareItem? compareItem;
  final _DisplayStatus status;

  const _EnrichedShoppingItem({
    required this.item,
    required this.compareItem,
    required this.status,
  });

  factory _EnrichedShoppingItem.from(
    ShoppingItem item,
    List<InventoryCompareItem> compareItems,
  ) {
    final compareItem = compareItems
        .where((compare) => compare.productId == item.productId && compare.unit == item.unit)
        .cast<InventoryCompareItem?>()
        .firstWhere((compare) => compare != null, orElse: () => null);

    if (compareItem == null) {
      return _EnrichedShoppingItem(
        item: item,
        compareItem: null,
        status: _DisplayStatus.missing,
      );
    }

    if (compareItem.status == 'pantry') {
      return _EnrichedShoppingItem(
        item: item,
        compareItem: compareItem,
        status: _DisplayStatus.pantry,
      );
    }

    if (compareItem.availableQuantity >= compareItem.requiredQuantity) {
      return _EnrichedShoppingItem(
        item: item,
        compareItem: compareItem,
        status: _DisplayStatus.enough,
      );
    }

    if (compareItem.availableQuantity > 0) {
      return _EnrichedShoppingItem(
        item: item,
        compareItem: compareItem,
        status: _DisplayStatus.partial,
      );
    }

    return _EnrichedShoppingItem(
      item: item,
      compareItem: compareItem,
      status: _DisplayStatus.missing,
    );
  }

  int get sortOrder => switch (status) {
        _DisplayStatus.missing => 0,
        _DisplayStatus.partial => 1,
        _DisplayStatus.enough => 2,
        _DisplayStatus.pantry => 3,
      };

  String get trailingText {
    final quantity = switch (status) {
      _DisplayStatus.pantry || _DisplayStatus.enough => 0.0,
      _DisplayStatus.partial => compareItem?.missingQuantity ?? item.quantity,
      _DisplayStatus.missing => item.quantity,
    };

    if (quantity <= 0) {
      return '-';
    }
    return '${_formatQuantity(quantity)} ${item.unit}';
  }

  String subtitle(String label) {
    switch (status) {
      case _DisplayStatus.partial:
        final available = compareItem?.availableQuantity ?? 0.0;
        final required = compareItem?.requiredQuantity ?? item.quantity;
        return '$label • ${_formatQuantity(available)} av ${_formatQuantity(required)} ${item.unit} hemma';
      case _DisplayStatus.enough:
      case _DisplayStatus.pantry:
        return label;
      case _DisplayStatus.missing:
        return '$label • ${_formatQuantity(item.quantity)} ${item.unit} behövs';
    }
  }

  String _formatQuantity(double value) {
    final normalized = value == value.roundToDouble() ? value.toStringAsFixed(0) : value.toStringAsFixed(1);
    return normalized.replaceAll(RegExp(r'\.0$'), '');
  }
}