import 'package:commons_core/commons_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';

import '../data/variety_repository.dart';
import '../i18n/strings.g.dart';
import '../services/coarse_location.dart';
import '../services/offer_outbox.dart';
import '../services/social_service.dart';
import '../services/social_settings.dart';
import '../state/offers_cubit.dart';
import 'theme.dart';

/// The market (redesign screens 04/05): discover seeds shared near you. Opens
/// an offer transport lazily; local-first, so it degrades to a "set up sharing"
/// prompt when no relay/area is configured or the network is unreachable.
class MarketScreen extends StatefulWidget {
  const MarketScreen({
    required this.social,
    required this.settings,
    this.location,
    this.outbox,
    super.key,
  });

  final SocialService social;
  final SocialSettings settings;

  /// Optional device-location source for the "use my location" shortcut; when
  /// null (tests, or a platform without it) the shortcut is hidden.
  final CoarseLocationProvider? location;

  /// Optional offline outbox: parks shares attempted with no network and flushes
  /// them on reconnect. Null in tests → sharing offline is simply a no-op.
  final OfferOutbox? outbox;

  @override
  State<MarketScreen> createState() => _MarketScreenState();
}

class _MarketScreenState extends State<MarketScreen> {
  OffersCubit? _cubit;
  bool _loading = true;

  @override
  void initState() {
    super.initState();
    _init();
  }

  Future<void> _init() async {
    // Only needed to flush the outbox; read here (while mounted) to avoid using
    // context across the awaits below. Null when there's no outbox (e.g. tests).
    final repo =
        widget.outbox != null ? context.read<VarietyRepository>() : null;
    setState(() => _loading = true);
    final cubit = await createOffersCubit(widget.social, widget.settings);
    final area = await widget.settings.areaGeohash();
    if (!mounted) {
      await cubit.close();
      return;
    }
    final previous = _cubit;
    setState(() {
      _cubit = cubit;
      _loading = false;
    });
    await previous?.close();
    if (area.isNotEmpty && cubit.isOnline) {
      // Flush anything parked while offline, then show what's out there.
      final outbox = widget.outbox;
      if (outbox != null && repo != null) {
        await flushOutbox(
          outbox: outbox,
          cubit: cubit,
          shareableLots: await repo.shareableLots(),
          authorPubkeyHex: widget.social.publicKeyHex,
          areaGeohash: area,
        );
      }
      if (mounted) await cubit.discover(area);
    }
  }

  Future<void> _openConfig() async {
    final changed = await showModalBottomSheet<bool>(
      context: context,
      isScrollControlled: true,
      builder: (_) =>
          _ConfigSheet(settings: widget.settings, location: widget.location),
    );
    if (changed == true) await _init();
  }

  /// Publishes the user's shared lots as offers to the network, tagged with the
  /// coarse area. Prompts for setup when the area isn't set.
  Future<void> _shareMine() async {
    final cubit = _cubit;
    if (cubit == null) return;
    final repo = context.read<VarietyRepository>();
    final messenger = ScaffoldMessenger.of(context);
    final t = context.t;
    final area = await widget.settings.areaGeohash();
    if (!mounted) return;
    if (area.isEmpty) {
      await _openConfig();
      return;
    }
    final lots = await repo.shareableLots();
    if (!mounted) return;
    if (lots.isEmpty) {
      messenger.showSnackBar(SnackBar(content: Text(t.market.nothingToShare)));
      return;
    }
    final ids = lots.map((l) => l.lotId).toList();

    // Offline: park it and tell the person we'll share it later.
    if (!cubit.isOnline) {
      await widget.outbox?.enqueue(ids);
      if (!mounted) return;
      messenger.showSnackBar(SnackBar(content: Text(t.market.queued)));
      return;
    }

    final count = await cubit.publishLots(
      lots,
      authorPubkeyHex: widget.social.publicKeyHex,
      areaGeohash: area,
    );
    if (!mounted) return;
    await widget.outbox?.remove(ids); // published now, so unpark any duplicates
    messenger.showSnackBar(SnackBar(content: Text(t.market.sharedCount(n: count))));
    await cubit.discover(area); // refresh — now including the just-shared lots
  }

  @override
  void dispose() {
    _cubit?.close();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final t = context.t;
    final cubit = _cubit;
    return Scaffold(
      appBar: AppBar(
        title: Text(t.market.title),
        actions: [
          if (cubit != null && (cubit.isOnline || widget.outbox != null))
            IconButton(
              key: const Key('market.shareMine'),
              icon: const Icon(Icons.ios_share_outlined),
              tooltip: t.market.shareMine,
              onPressed: _shareMine,
            ),
          IconButton(
            key: const Key('market.config'),
            icon: const Icon(Icons.tune),
            tooltip: t.market.configTitle,
            onPressed: _openConfig,
          ),
        ],
      ),
      body: _loading || cubit == null
          ? const Center(child: CircularProgressIndicator())
          : BlocProvider.value(
              value: cubit,
              child: MarketBody(
                onConfigure: _openConfig,
                selfPubkey: widget.social.publicKeyHex,
              ),
            ),
    );
  }
}

class MarketBody extends StatelessWidget {
  const MarketBody({
    required this.onConfigure,
    this.selfPubkey,
    super.key,
  });

  final VoidCallback onConfigure;

  /// This user's own pubkey, so we don't offer to message our own listings.
  final String? selfPubkey;

  @override
  Widget build(BuildContext context) {
    final t = context.t;
    return BlocBuilder<OffersCubit, OffersState>(
      builder: (context, state) {
        if (!context.read<OffersCubit>().isOnline) {
          return _EmptyState(
            icon: Icons.wifi_tethering_off_outlined,
            title: t.market.notSetUp,
            body: t.market.notSetUpBody,
            actionLabel: t.market.setUp,
            onAction: onConfigure,
          );
        }
        if (state.searching && state.offers.isEmpty) {
          return Center(
            child: Column(
              mainAxisSize: MainAxisSize.min,
              children: [
                const CircularProgressIndicator(),
                const SizedBox(height: 16),
                Text(t.market.searching,
                    style: const TextStyle(color: seedMuted)),
              ],
            ),
          );
        }
        if (state.offers.isEmpty) {
          return _EmptyState(
            icon: Icons.grass_outlined,
            title: t.market.empty,
            body: '',
          );
        }
        return ListView.separated(
          padding: const EdgeInsets.all(16),
          itemCount: state.offers.length,
          separatorBuilder: (_, _) => const SizedBox(height: 12),
          itemBuilder: (context, i) {
            final o = state.offers[i];
            final mine = o.authorPubkeyHex == selfPubkey;
            return _OfferCard(
              offer: o,
              mine: mine,
              onContact: mine ? null : () => context.go('/chat/${o.authorPubkeyHex}'),
            );
          },
        );
      },
    );
  }
}

/// One discovered offer. Human words for the reciprocity mode; coarse "near you"
/// instead of any precise location; price only when it's a sale.
class _OfferCard extends StatelessWidget {
  const _OfferCard({required this.offer, this.mine = false, this.onContact});

  final Offer offer;

  /// Whether this is the current user's own listing (shown with a "You" badge).
  final bool mine;

  /// Opens a private chat with the author; null for our own listings.
  final VoidCallback? onContact;

  @override
  Widget build(BuildContext context) {
    final t = context.t;
    return Container(
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(14),
        border: Border.all(color: seedOutline),
      ),
      padding: const EdgeInsets.all(16),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Expanded(
                child: Text(
                  offer.summary,
                  style: const TextStyle(
                    fontSize: 17,
                    fontWeight: FontWeight.w500,
                    color: seedOnSurface,
                  ),
                ),
              ),
              if (mine) ...[
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                  decoration: BoxDecoration(
                    color: seedGreen,
                    borderRadius: BorderRadius.circular(20),
                  ),
                  child: Text(
                    t.market.mine,
                    style: const TextStyle(
                      color: Colors.white,
                      fontSize: 11,
                      fontWeight: FontWeight.w700,
                    ),
                  ),
                ),
                const SizedBox(width: 6),
              ],
              _TypeChip(label: _offerTypeLabel(t, offer.type)),
            ],
          ),
          const SizedBox(height: 6),
          Row(
            children: [
              const Icon(Icons.place_outlined, size: 15, color: seedMuted),
              const SizedBox(width: 4),
              Text(t.market.near, style: const TextStyle(color: seedMuted, fontSize: 13)),
              const Spacer(),
              if (offer.type == OfferType.sale && offer.priceAmount != null)
                Text(
                  '${offer.priceAmount} ${offer.priceCurrency ?? ''}'.trim(),
                  style: const TextStyle(
                    color: seedOnSurface,
                    fontWeight: FontWeight.w600,
                  ),
                ),
              if (onContact != null)
                TextButton.icon(
                  onPressed: onContact,
                  icon: const Icon(Icons.chat_bubble_outline, size: 16),
                  label: Text(t.market.contact),
                ),
            ],
          ),
        ],
      ),
    );
  }
}

String _offerTypeLabel(Translations t, OfferType type) => switch (type) {
      OfferType.gift => t.share.gift,
      OfferType.exchange => t.share.exchange,
      OfferType.sale => t.share.sell,
      OfferType.wanted => t.market.wanted,
      OfferType.lend => t.share.exchange,
    };

class _TypeChip extends StatelessWidget {
  const _TypeChip({required this.label});

  final String label;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
      decoration: BoxDecoration(
        color: seedPrimaryContainer,
        borderRadius: BorderRadius.circular(20),
      ),
      child: Text(
        label,
        style: const TextStyle(
          color: seedOnPrimaryContainer,
          fontSize: 12,
          fontWeight: FontWeight.w600,
        ),
      ),
    );
  }
}

class _EmptyState extends StatelessWidget {
  const _EmptyState({
    required this.icon,
    required this.title,
    required this.body,
    this.actionLabel,
    this.onAction,
  });

  final IconData icon;
  final String title;
  final String body;
  final String? actionLabel;
  final VoidCallback? onAction;

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Padding(
        padding: const EdgeInsets.all(32),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            Icon(icon, size: 56, color: seedMuted),
            const SizedBox(height: 16),
            Text(
              title,
              textAlign: TextAlign.center,
              style: const TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.w500,
                color: seedOnSurface,
              ),
            ),
            if (body.isNotEmpty) ...[
              const SizedBox(height: 8),
              Text(
                body,
                textAlign: TextAlign.center,
                style: const TextStyle(color: seedMuted, fontSize: 14),
              ),
            ],
            if (actionLabel != null && onAction != null) ...[
              const SizedBox(height: 20),
              FilledButton(onPressed: onAction, child: Text(actionLabel!)),
            ],
          ],
        ),
      ),
    );
  }
}

/// Coarse-area + community-server setup. Kept behind progressive disclosure — a
/// power-user surface — with human-worded labels.
class _ConfigSheet extends StatefulWidget {
  const _ConfigSheet({required this.settings, this.location});

  final SocialSettings settings;
  final CoarseLocationProvider? location;

  @override
  State<_ConfigSheet> createState() => _ConfigSheetState();
}

class _ConfigSheetState extends State<_ConfigSheet> {
  final _area = TextEditingController();
  bool _loading = true;
  bool _locationBusy = false;
  String? _locationError;

  @override
  void initState() {
    super.initState();
    _load();
  }

  Future<void> _load() async {
    _area.text = await widget.settings.areaGeohash();
    if (mounted) setState(() => _loading = false);
  }

  Future<void> _save() async {
    // Relays are managed automatically (default community servers, dead ones
    // skipped by the pool) — the user only sets their area here.
    await widget.settings.setAreaGeohash(_area.text);
    if (mounted) Navigator.of(context).pop(true);
  }

  /// Fills the area from the device's approximate location, reduced to a coarse
  /// geohash so no precise fix is stored.
  Future<void> _useLocation() async {
    final provider = widget.location;
    if (provider == null || _locationBusy) return;
    setState(() {
      _locationBusy = true;
      _locationError = null;
    });
    final loc = await provider.currentCoarseLatLon();
    if (!mounted) return;
    setState(() {
      _locationBusy = false;
      if (loc == null) {
        _locationError = context.t.market.locationFailed;
      } else {
        _area.text = Geohash.encode(loc.lat, loc.lon, precision: 5);
      }
    });
  }

  @override
  void dispose() {
    _area.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    final t = context.t;
    return Padding(
      padding: EdgeInsets.only(
        left: 20,
        right: 20,
        top: 20,
        bottom: MediaQuery.of(context).viewInsets.bottom + 20,
      ),
      child: _loading
          ? const SizedBox(height: 120, child: Center(child: CircularProgressIndicator()))
          : SingleChildScrollView(
              child: Column(
              mainAxisSize: MainAxisSize.min,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                Text(
                  t.market.configTitle,
                  style: const TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w600,
                    color: seedTitle,
                  ),
                ),
                const SizedBox(height: 8),
                Text(
                  t.market.setupIntro,
                  style: const TextStyle(color: seedMuted, fontSize: 13, height: 1.4),
                ),
                const SizedBox(height: 16),
                TextField(
                  key: const Key('market.area'),
                  controller: _area,
                  decoration: InputDecoration(
                    labelText: t.market.areaLabel,
                    helperText: t.market.areaHelp,
                    helperMaxLines: 3,
                  ),
                ),
                if (widget.location != null)
                  Align(
                    alignment: AlignmentDirectional.centerStart,
                    child: TextButton.icon(
                      key: const Key('market.useLocation'),
                      icon: _locationBusy
                          ? const SizedBox(
                              width: 16,
                              height: 16,
                              child: CircularProgressIndicator(strokeWidth: 2),
                            )
                          : const Icon(Icons.my_location, size: 18),
                      label: Text(t.market.useLocation),
                      onPressed: _locationBusy ? null : _useLocation,
                    ),
                  ),
                if (_locationError != null)
                  Padding(
                    padding: const EdgeInsets.only(top: 4),
                    child: Text(
                      _locationError!,
                      style: const TextStyle(color: Color(0xFFB3261E), fontSize: 12),
                    ),
                  ),
                const SizedBox(height: 20),
                FilledButton(
                  key: const Key('market.save'),
                  onPressed: _save,
                  child: Text(t.market.save),
                ),
              ],
            ),
          ),
    );
  }
}
