import "package:flutter/material.dart";

import "../format.dart";
import "../i18n.dart";
import "../main.dart";
import "../models.dart";
import "../theme.dart";

/// The connected-service tab on a car: everything the manufacturer's own app
/// knows about it, fetched live. Mirrors the web ProviderPanel.
///
/// Two things shape what is below. The data is read under *this* user's account
/// — so a car shared from someone else shows nothing here unless the vehicle is
/// on this user's account too, and the tab says so rather than failing. And the
/// sections are whatever the plugin exposes, rendered from the server's
/// flattened key/value pairs: nothing here knows a single upstream field name,
/// so a provider adding a field surfaces it without a change to this file.
///
/// Unlike the web, the headline readings cannot be rearranged here — a drag on a
/// touch screen is the tab bar's gesture. The stored arrangement is still
/// honoured, so an order set on the web carries over.
class ProviderTab extends StatefulWidget {
  final Car car;

  /// Called with the updated car after a link, unlink or odometer sync, so the
  /// page holds one copy of it and the tab bar can pick up a new provider link.
  final void Function(Car) onCarUpdated;

  const ProviderTab({super.key, required this.car, required this.onCarUpdated});

  @override
  State<ProviderTab> createState() => _ProviderTabState();
}

class _ProviderTabState extends State<ProviderTab> {
  ProviderSnapshot? _snap;
  bool _loading = false;
  bool _syncing = false;
  String? _error;

  // The connect flow, for a car with no link yet (added by hand, or from before
  // the feature existed).
  List<VehicleProvider> _providers = const [];
  String _linkProvider = "";
  List<ProviderVehicle> _linkVehicles = const [];
  String _linkVehicleId = "";
  bool _linkLoading = false;
  bool _linking = false;

  bool get _linked => widget.car.provider.isNotEmpty;

  List<VehicleProvider> get _connectable =>
      _providers.where((p) => p.connected).toList();

  String get _label {
    final fromSnap = _snap?.label ?? "";
    if (fromSnap.isNotEmpty) return fromSnap;
    for (final p in _providers) {
      if (p.id == widget.car.provider) return p.label;
    }
    return widget.car.provider.isEmpty ? t("car.tabs.connected") : widget.car.provider;
  }

  @override
  void initState() {
    super.initState();
    if (_linked) {
      _load();
    } else {
      _loadProviders();
    }
  }

  @override
  void didUpdateWidget(ProviderTab old) {
    super.didUpdateWidget(old);
    // Linking from the connect card replaces the car, which is when the live
    // snapshot becomes fetchable at all.
    if (old.car.provider != widget.car.provider) {
      if (_linked) {
        _load();
      } else {
        setState(() => _snap = null);
        _loadProviders();
      }
    }
  }

  Future<void> _load() async {
    setState(() {
      _loading = true;
      _error = null;
    });
    try {
      final snap = await apiClient.getCarProvider(widget.car.id);
      if (mounted) setState(() => _snap = snap);
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _loading = false);
    }
  }

  Future<void> _loadProviders() async {
    try {
      final list = await apiClient.listVehicleProviders();
      if (!mounted) return;
      setState(() {
        _providers = list;
        _linkProvider = _connectable.isEmpty ? "" : _connectable.first.id;
      });
      await _loadLinkVehicles();
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    }
  }

  Future<void> _loadLinkVehicles() async {
    setState(() {
      _linkVehicles = const [];
      _linkVehicleId = "";
    });
    if (_linkProvider.isEmpty) return;
    setState(() => _linkLoading = true);
    try {
      final list = await apiClient.listProviderVehicles(_linkProvider);
      if (!mounted) return;
      // Prefer the vehicle whose VIN matches the car — usually the only guess
      // needed.
      final vin = widget.car.vin.trim().toUpperCase();
      String pick = list.isEmpty ? "" : list.first.id;
      if (vin.isNotEmpty) {
        for (final v in list) {
          if (v.vin.toUpperCase() == vin) {
            pick = v.id;
            break;
          }
        }
      }
      setState(() {
        _linkVehicles = list;
        _linkVehicleId = pick;
      });
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _linkLoading = false);
    }
  }

  Future<void> _connect() async {
    if (_linkVehicleId.isEmpty) return;
    setState(() {
      _linking = true;
      _error = null;
    });
    try {
      final car = await apiClient.linkCarProvider(widget.car.id,
          provider: _linkProvider, vehicleId: _linkVehicleId);
      widget.onCarUpdated(car);
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _linking = false);
    }
  }

  Future<void> _disconnect() async {
    final ok = await showDialog<bool>(
      context: context,
      builder: (ctx) => AlertDialog(
        content: Text(t("car.provider.unlinkConfirm", params: {"label": _label})),
        actions: [
          TextButton(
              onPressed: () => Navigator.pop(ctx, false), child: Text(t("common.cancel"))),
          FilledButton(
            style: FilledButton.styleFrom(backgroundColor: DriverVault.danger),
            onPressed: () => Navigator.pop(ctx, true),
            child: Text(t("car.provider.unlink")),
          ),
        ],
      ),
    );
    if (ok != true) return;
    setState(() => _error = null);
    try {
      final car = await apiClient.linkCarProvider(widget.car.id);
      if (mounted) setState(() => _snap = null);
      widget.onCarUpdated(car);
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    }
  }

  /// Only the odometer is written back: it is the one provider reading the rest
  /// of the app computes from (service intervals, km-triggered reminders), and
  /// the one the user would otherwise retype after every drive.
  Future<void> _applyOdometer() async {
    setState(() {
      _syncing = true;
      _error = null;
    });
    try {
      final car = await apiClient.syncCarProvider(widget.car.id, include: {
        "identity": false,
        "fuelType": false,
        "dates": false,
        "odometer": true,
      });
      widget.onCarUpdated(car);
      await _load();
    } catch (e) {
      if (mounted) setState(() => _error = e.toString());
    } finally {
      if (mounted) setState(() => _syncing = false);
    }
  }

  /// The readings in the car's stored arrangement. A reading the arrangement
  /// does not name — one the provider only started reporting later — follows the
  /// arranged ones rather than appearing in the middle.
  List<ProviderMetric> get _metrics {
    final reported = _snap?.metrics ?? const <ProviderMetric>[];
    final order = widget.car.metricOrder;
    if (order.isEmpty) return reported;
    final rank = {for (var i = 0; i < order.length; i++) order[i]: i};
    final list = [...reported];
    // A stable sort, so two unarranged readings keep the order they came in.
    list.sort((a, b) => (rank[a.key] ?? order.length).compareTo(rank[b.key] ?? order.length));
    return list;
  }

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.fromLTRB(12, 12, 12, 80),
      children: [
        if (_error != null) ...[
          _ErrorBanner(_error!),
          const SizedBox(height: 12),
        ],
        if (_linked) ..._linkedBody(context) else ..._connectBody(context),
      ],
    );
  }

  // --- linked: the live snapshot -------------------------------------------

  List<Widget> _linkedBody(BuildContext context) {
    final snap = _snap;
    return [
      Row(
        children: [
          Expanded(
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(_label,
                    style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700)),
                Text(t("car.provider.subtitle", params: {"label": _label}),
                    style: TextStyle(fontSize: 12, color: DriverVault.muted(context))),
              ],
            ),
          ),
          IconButton(
            tooltip: _loading ? t("car.provider.refreshing") : t("car.provider.refresh"),
            icon: const Icon(Icons.refresh),
            onPressed: _loading ? null : _load,
          ),
          if (widget.car.canWrite)
            IconButton(
              tooltip: t("car.provider.unlink"),
              icon: const Icon(Icons.link_off, color: DriverVault.danger),
              onPressed: _disconnect,
            ),
        ],
      ),
      const SizedBox(height: 8),
      if (snap == null)
        const Center(child: Padding(padding: EdgeInsets.all(24), child: CircularProgressIndicator()))
      else if (snap.unavailable)
        _Card(
          child: Column(
            children: [
              Text(snap.detail,
                  textAlign: TextAlign.center,
                  style: const TextStyle(color: DriverVault.warning)),
              const SizedBox(height: 6),
              Text(t("car.provider.own"),
                  textAlign: TextAlign.center,
                  style: TextStyle(fontSize: 11, color: DriverVault.muted(context))),
            ],
          ),
        )
      else ...[
        if (_metrics.isNotEmpty) _readings(context),
        if (snap.suggestedCurrentKm > 0 && widget.car.canWrite) _odometerOffer(context, snap),
        if (snap.vehicle != null) _vehicleCard(context, snap.vehicle!),
        ...snap.sections.map((s) => _sectionCard(context, s)),
        const SizedBox(height: 8),
        Text(
          "${t("car.provider.updated", params: {"time": formatDateTime(snap.fetchedAt)})}"
          " · ${t("car.provider.own")}",
          style: TextStyle(fontSize: 11, color: DriverVault.muted(context)),
        ),
      ],
    ];
  }

  Widget _readings(BuildContext context) => _Card(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(t("car.provider.readings"),
                style: const TextStyle(fontWeight: FontWeight.w600)),
            const SizedBox(height: 8),
            Wrap(
              spacing: 16,
              runSpacing: 8,
              children: [
                for (final m in _metrics)
                  SizedBox(
                    width: 150,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Text(t("car.provider.metrics.${m.key}"),
                            style: TextStyle(fontSize: 11, color: DriverVault.muted(context))),
                        Text(m.unit.isEmpty ? m.value : "${m.value} ${m.unit}",
                            style: DriverVault.mono(context,
                                size: 13,
                                weight: FontWeight.w600,
                                color: Theme.of(context).colorScheme.onSurface)),
                      ],
                    ),
                  ),
              ],
            ),
          ],
        ),
      );

  Widget _odometerOffer(BuildContext context, ProviderSnapshot snap) => _Card(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(t("car.provider.odometerSuggest", params: {
              "label": _label,
              "km": formatKm(snap.suggestedCurrentKm),
            })),
            const SizedBox(height: 8),
            Align(
              alignment: Alignment.centerRight,
              child: FilledButton(
                onPressed: _syncing ? null : _applyOdometer,
                child: Text(_syncing
                    ? t("car.provider.updatingOdometer")
                    : t("car.provider.updateOdometer")),
              ),
            ),
          ],
        ),
      );

  Widget _vehicleCard(BuildContext context, ProviderVehicle v) => _Card(
        padding: EdgeInsets.zero,
        child: ExpansionTile(
          initiallyExpanded: true,
          shape: const Border(),
          collapsedShape: const Border(),
          title: Text(t("car.provider.vehicle"),
              style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
          childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
          children: [
            Row(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(v.name, style: const TextStyle(fontWeight: FontWeight.w700)),
                      if (v.subtitle.isNotEmpty)
                        Text(v.subtitle,
                            style: TextStyle(fontSize: 12, color: DriverVault.muted(context))),
                      if (v.vin.isNotEmpty)
                        Text(v.vin,
                            style: DriverVault.mono(context,
                                size: 11, color: DriverVault.muted(context))),
                    ],
                  ),
                ),
                if (v.imageUrl.isNotEmpty)
                  // The provider's own CDN, so it can fail on a phone with no
                  // route to it — a broken image must not take the tab with it.
                  Image.network(v.imageUrl,
                      height: 64,
                      fit: BoxFit.contain,
                      errorBuilder: (_, __, ___) => const SizedBox.shrink()),
              ],
            ),
            if (v.fields.isNotEmpty) _fieldsExpander(context, t("car.provider.allFields"), v.fields),
          ],
        ),
      );

  Widget _sectionCard(BuildContext context, ProviderSection sec) => _Card(
        padding: EdgeInsets.zero,
        child: ExpansionTile(
          shape: const Border(),
          collapsedShape: const Border(),
          title: Row(
            children: [
              Expanded(
                child: Text(t("car.provider.sections.${sec.id}"),
                    style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w600)),
              ),
              // A failed section says so in the collapsed header, but only as a
              // badge: the provider's own message can run to hundreds of
              // characters, which belongs in the body with the rest of it.
              if (sec.status == "error")
                Container(
                  padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
                  decoration: BoxDecoration(
                    color: DriverVault.isDark(context)
                        ? DriverVault.warningSoftDark
                        : DriverVault.warningSoft,
                    borderRadius: BorderRadius.circular(6),
                  ),
                  child: Text(t("car.provider.sectionFailed"),
                      style: const TextStyle(fontSize: 11, color: DriverVault.warning)),
                ),
            ],
          ),
          childrenPadding: const EdgeInsets.fromLTRB(14, 0, 14, 12),
          children: [
            if (sec.status == "error")
              Align(
                alignment: Alignment.centerLeft,
                child: Text(sec.error,
                    style: const TextStyle(fontSize: 12, color: DriverVault.warning)),
              )
            else if (sec.status == "empty")
              Align(
                alignment: Alignment.centerLeft,
                child: Text(t("car.provider.sectionEmpty"),
                    style: TextStyle(fontSize: 12, color: DriverVault.muted(context))),
              )
            else ...[
              _fieldList(context, sec.fields),
              if (sec.truncated)
                Padding(
                  padding: const EdgeInsets.only(top: 6),
                  child: Text(
                    t("car.provider.truncated", params: {"n": sec.fields.length}),
                    style: const TextStyle(fontSize: 11, color: DriverVault.warning),
                  ),
                ),
            ],
          ],
        ),
      );

  Widget _fieldsExpander(BuildContext context, String label, List<ProviderField> fields) =>
      Theme(
        // The nested expander would otherwise draw its own divider lines inside
        // the card it is already sitting in.
        data: Theme.of(context).copyWith(dividerColor: Colors.transparent),
        child: ExpansionTile(
          tilePadding: EdgeInsets.zero,
          title: Text(label,
              style: TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.w600,
                  color: DriverVault.brandOnTint(context))),
          childrenPadding: EdgeInsets.zero,
          children: [_fieldList(context, fields)],
        ),
      );

  Widget _fieldList(BuildContext context, List<ProviderField> fields) => Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          for (final f in fields)
            Padding(
              padding: const EdgeInsets.symmetric(vertical: 3),
              child: Row(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  SizedBox(
                    width: 140,
                    child: Text(f.key,
                        style: DriverVault.mono(context,
                            size: 11, color: DriverVault.muted(context))),
                  ),
                  Expanded(child: Text(f.value, style: const TextStyle(fontSize: 12))),
                ],
              ),
            ),
        ],
      );

  // --- not linked: offer to connect this car to a vehicle -------------------

  List<Widget> _connectBody(BuildContext context) {
    if (_connectable.isEmpty) {
      return [
        _Card(
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(t("car.provider.connectTitle"),
                  style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700)),
              const SizedBox(height: 6),
              Text(t("car.provider.noProviders"),
                  style: TextStyle(fontSize: 12, color: DriverVault.muted(context))),
            ],
          ),
        ),
      ];
    }
    return [
      _Card(
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(t("car.provider.connectTitle"),
                style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w700)),
            const SizedBox(height: 4),
            Text(t("car.provider.connectHint"),
                style: TextStyle(fontSize: 12, color: DriverVault.muted(context))),
            const SizedBox(height: 12),
            if (_connectable.length > 1) ...[
              DropdownButtonFormField<String>(
                initialValue: _linkProvider.isEmpty ? null : _linkProvider,
                decoration: InputDecoration(
                    labelText: t("forms.import.service"), border: const OutlineInputBorder()),
                items: [
                  for (final p in _connectable)
                    DropdownMenuItem(value: p.id, child: Text(p.label)),
                ],
                onChanged: (v) {
                  if (v == null) return;
                  setState(() => _linkProvider = v);
                  _loadLinkVehicles();
                },
              ),
              const SizedBox(height: 12),
            ],
            if (_linkLoading)
              Text(t("forms.import.loadingVehicles"),
                  style: TextStyle(fontSize: 12, color: DriverVault.muted(context)))
            else if (_linkVehicles.isEmpty)
              Text(t("forms.import.noVehicles"),
                  style: TextStyle(fontSize: 12, color: DriverVault.muted(context)))
            else
              DropdownButtonFormField<String>(
                key: ValueKey("$_linkProvider/$_linkVehicleId"),
                initialValue: _linkVehicleId.isEmpty ? null : _linkVehicleId,
                isExpanded: true,
                decoration: InputDecoration(
                    labelText: t("forms.import.selectVehicle"),
                    border: const OutlineInputBorder()),
                items: [
                  for (final v in _linkVehicles)
                    DropdownMenuItem(
                      value: v.id,
                      child: Text(v.vin.isEmpty ? v.name : "${v.name} · ${v.vin}",
                          overflow: TextOverflow.ellipsis),
                    ),
                ],
                onChanged: (v) => setState(() => _linkVehicleId = v ?? ""),
              ),
            if (widget.car.canWrite) ...[
              const SizedBox(height: 12),
              Align(
                alignment: Alignment.centerRight,
                child: FilledButton(
                  onPressed: _linkVehicleId.isEmpty || _linking ? null : _connect,
                  child: Text(_linking
                      ? t("car.provider.connecting")
                      : t("car.provider.connectSubmit")),
                ),
              ),
            ],
          ],
        ),
      ),
    ];
  }
}

/// The card chrome the rest of the car screen uses, repeated here so this tab
/// sits flush with the others.
class _Card extends StatelessWidget {
  final Widget child;
  final EdgeInsetsGeometry padding;
  const _Card({required this.child, this.padding = const EdgeInsets.all(14)});

  @override
  Widget build(BuildContext context) => Card(
        elevation: 0,
        margin: const EdgeInsets.only(bottom: 8),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(12),
          side: BorderSide(
              color: DriverVault.isDark(context) ? DriverVault.darkBorder : DriverVault.ink100),
        ),
        child: Padding(padding: padding, child: child),
      );
}

class _ErrorBanner extends StatelessWidget {
  final String message;
  const _ErrorBanner(this.message);

  @override
  Widget build(BuildContext context) => Container(
        width: double.infinity,
        padding: const EdgeInsets.all(12),
        decoration: BoxDecoration(
          color: DriverVault.isDark(context)
              ? DriverVault.dangerSoftDark
              : DriverVault.dangerSoft,
          borderRadius: BorderRadius.circular(10),
        ),
        child: Text(message,
            style: const TextStyle(color: DriverVault.danger, fontWeight: FontWeight.w500)),
      );
}
