import 'dart:convert';
import 'dart:io';

import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:get/get.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:uuid/uuid.dart';

import '../models/contact.dart';
import 'app_database.dart';

/// A `contacts` Supabase-tábla műveletei + OFFLINE OUTBOX.
///
/// Modell: a kapcsolatok OTTHONA a Supabase (jogosultsághoz kötött, RLS).
/// A lokális `contacts_outbox` NEM tükör, hanem átmeneti PUFFER: csak a
/// még fel nem töltött, offline rögzített kapcsolatok élnek benne.
///
///  * Íráskor van net → egyből a Supabase-re (mint eddig).
///  * Íráskor nincs net → az outboxba `pending` jelöléssel; a lista a
///    szerver-adatok MELLETT ezeket is mutatja (a UI "feltöltésre vár"
///    jelzéssel).
///  * Net visszatér → flush(): feltölt, és CSAK a Supabase visszaigazolása
///    UTÁN törli a lokális példányt — így hálózati flikkernél sem vész el
///    adat. A local_uuid kulcs idempotenssé teszi az ismételt próbát.
///
/// Korlát (szándékos): ez csak LÉTREHOZÁSRA offline-képes. Egy már
/// felküldött kapcsolat szerkesztése/törlése online művelet (a teljes
/// kétirányú szinkron komplexitását nem hozzuk be). Az outboxban lévő,
/// még fel nem töltött sor helyben szerkeszthető/törölhető.
class ContactService extends GetxService {
  static ContactService get to => Get.find();

  static const _uuid = Uuid();
  SupabaseClient get _client => Supabase.instance.client;
  AppDatabase get _db => AppDatabase.instance;

  Future<bool> get _isOnline async {
    final r = await Connectivity().checkConnectivity();
    return r.any((c) => c != ConnectivityResult.none);
  }

  // ═════════════════════════════════════════════════════════════════
  // Olvasás — szerver + lokális pending összefésülve
  // ═════════════════════════════════════════════════════════════════

  /// A megjelenítendő lista: a Supabase-ből lehúzott kapcsolatok ELÉ
  /// fűzve a még fel nem töltött (pending) helyi sorok. A pending sorokat
  /// az `isPending` jelöli — a UI kis felhő-ikonnal mutatja őket.
  Future<List<ContactWithState>> listMerged(String projectId) async {
    final pending = (await _db.listPendingContacts(projectId: projectId))
        .map((m) => ContactWithState(
              contact: Contact.fromMap(m),
              isPending: true,
              localUuid: m['local_uuid'] as String,
            ))
        .toList();

    List<ContactWithState> remote = [];
    try {
      final rows = await _client
          .from('terepi_seged_contacts')
          .select()
          .eq('project_id', projectId)
          .order('name', ascending: true);
      remote = rows
          .map((r) =>
              ContactWithState(contact: Contact.fromMap(r), isPending: false))
          .toList();
    } catch (_) {
      // Offline / RLS: a szerver-lista nem érhető el, de a pendingeket
      // ilyenkor is meg tudjuk mutatni.
      if (pending.isEmpty) rethrow;
    }

    return [...pending, ...remote];
  }

  // ═════════════════════════════════════════════════════════════════
  // Írás — online egyből, offline pufferbe
  // ═════════════════════════════════════════════════════════════════

  /// Új kapcsolat vagy szerver-oldali frissítés.
  /// Visszaadja, hogy a művelet OFFLINE pufferbe került-e (true = várólistán).
  Future<bool> save(Contact c) async {
    // Meglévő (szerver-oldali) rekord frissítése CSAK online — lásd korlát.
    final isUpdate = c.id != null;

    if (await _isOnline) {
      try {
        await _client.from('terepi_seged_contacts').upsert(c.toWriteMap());
        return false; // felment
      } catch (e) {
        if (isUpdate) rethrow; // frissítést nem pufferelünk
        // Új rekord + online hiba (pl. pillanatnyi kiesés) → pufferbe.
      }
    } else if (isUpdate) {
      throw ContactOfflineException(
          'Meglévő kapcsolat szerkesztéséhez internet szükséges. '
          'Új kapcsolat rögzítése offline is működik.');
    }

    // Offline (vagy online-hiba) új rekord → outbox.
    await _db.insertPendingContact({
      'local_uuid': _uuid.v4(),
      'project_id': c.projectId,
      'name': c.name.trim(),
      'address': c.address.trim(),
      'phone': c.phone.trim(),
      'email': c.email.trim(),
      'note': c.note.trim(),
      'created_at': DateTime.now().toIso8601String(),
    });
    return true; // várólistán
  }

  /// Törlés. Pending (helyi) sor: azonnal a lokálisból. Szerver-oldali
  /// rekord: online művelet.
  Future<void> delete({String? id, String? localUuid}) async {
    if (localUuid != null) {
      await _db.deletePendingContact(localUuid);
      return;
    }
    if (id != null) {
      await _client.from('terepi_seged_contacts').delete().eq('id', id);
    }
  }

  /// Egyetlen pending sor mezőinek frissítése (amíg helyben van).
  Future<void> updatePending(String localUuid, Contact c) async {
    await _db.updatePendingContact(localUuid, {
      'name': c.name.trim(),
      'address': c.address.trim(),
      'phone': c.phone.trim(),
      'email': c.email.trim(),
      'note': c.note.trim(),
    });
  }

  // ═════════════════════════════════════════════════════════════════
  // Flush — a TsSyncService hívja net-visszatéréskor
  // ═════════════════════════════════════════════════════════════════

  /// Feltölti a pending kapcsolatokat, és MINDEGYIKET CSAK a Supabase
  /// visszaigazolása után törli a lokálisból. Visszaadja a feltöltöttek
  /// számát. Hiba (pl. RLS) esetén a sor a pufferben marad.
  Future<int> flush() async {
    if (_client.auth.currentUser == null) return 0;
    if (!await _isOnline) return 0;

    final rows = await _db.listPendingContacts();
    var uploaded = 0;
    for (final m in rows) {
      try {
        // local_uuid mint kliens-kulcs → az ismételt próba idempotens.
        await _client.from('terepi_seged_contacts').upsert({
          'client_uuid': m['local_uuid'],
          'project_id': m['project_id'],
          'name': m['name'],
          'address': m['address'],
          'phone': m['phone'],
          'email': m['email'],
          'note': m['note'],
        }, onConflict: 'client_uuid', ignoreDuplicates: true);

        // Sikeres felküldés → a lokális példány törölhető.
        await _db.deletePendingContact(m['local_uuid'] as String);
        uploaded++;
      } catch (_) {
        // A sor marad a pufferben, a következő flush újrapróbálja.
      }
    }
    return uploaded;
  }

  Future<int> pendingCount(String projectId) =>
      _db.countPendingContacts(projectId: projectId);

  // ═════════════════════════════════════════════════════════════════
  // CSV import (változatlan a korábbihoz képest)
  // ═════════════════════════════════════════════════════════════════

  static Future<List<Contact>> parseCsv(File file,
      {required String projectId}) async {
    final bytes = await file.readAsBytes();
    String text;
    try {
      text = utf8.decode(bytes);
    } catch (_) {
      text = latin1.decode(bytes);
    }
    if (text.isNotEmpty && text.codeUnitAt(0) == 0xFEFF) {
      text = text.substring(1);
    }

    final lines = text
        .split(RegExp(r'\r\n|\r|\n'))
        .where((l) => l.trim().isNotEmpty)
        .toList();
    if (lines.isEmpty) return [];

    String delimiter = ';';
    var best = -1;
    for (final d in [';', ',', '\t']) {
      final n = d.allMatches(lines.first).length;
      if (n > best) {
        best = n;
        delimiter = d;
      }
    }

    List<String> split(String line) {
      final cells = <String>[];
      final sb = StringBuffer();
      var inQ = false;
      for (var i = 0; i < line.length; i++) {
        final ch = line[i];
        if (ch == '"') {
          inQ = !inQ;
        } else if (ch == delimiter && !inQ) {
          cells.add(sb.toString().trim());
          sb.clear();
        } else {
          sb.write(ch);
        }
      }
      cells.add(sb.toString().trim());
      return cells;
    }

    final rows = lines.map(split).toList();
    final first = rows.first.map((c) => c.toLowerCase()).toList();
    const nameKeys = ['name', 'nev', 'név', 'kapcsolat'];
    const addrKeys = ['address', 'cim', 'cím', 'lakcim', 'lakcím'];
    const phoneKeys = ['phone', 'telefon', 'tel', 'mobil'];
    const emailKeys = ['email', 'e-mail', 'mail'];
    const noteKeys = ['note', 'megjegyzes', 'megjegyzés', 'jegyzet'];

    bool hasAny(String c, List<String> keys) => keys.any(c.contains);
    final hasHeader = first.any((c) =>
        hasAny(c, nameKeys) ||
        hasAny(c, addrKeys) ||
        hasAny(c, phoneKeys) ||
        hasAny(c, emailKeys));

    int idx(List<String> keys, int fallback) {
      if (!hasHeader) return fallback;
      return first.indexWhere((c) => hasAny(c, keys));
    }

    final iName = idx(nameKeys, 0);
    final iAddr = idx(addrKeys, 1);
    final iPhone = idx(phoneKeys, 2);
    final iEmail = idx(emailKeys, 3);
    final iNote = idx(noteKeys, 4);

    final dataRows = hasHeader ? rows.skip(1) : rows;
    String cell(List<String> r, int i) => (i >= 0 && i < r.length) ? r[i] : '';

    final contacts = <Contact>[];
    for (final r in dataRows) {
      final name = cell(r, iName);
      if (name.isEmpty) continue;
      contacts.add(Contact(
        projectId: projectId,
        name: name,
        address: cell(r, iAddr),
        phone: cell(r, iPhone),
        email: cell(r, iEmail),
        note: cell(r, iNote),
      ));
    }
    return contacts;
  }

  /// Import: online egyben a Supabase-be, offline az outboxba (soronként).
  /// Visszaadja (felküldött, várólistára tett) párt.
  Future<({int uploaded, int queued})> importMany(
      List<Contact> contacts) async {
    if (contacts.isEmpty) return (uploaded: 0, queued: 0);

    if (await _isOnline) {
      try {
        await _client
            .from('terepi_seged_contacts')
            .insert(contacts.map((c) => c.toWriteMap()).toList());
        return (uploaded: contacts.length, queued: 0);
      } catch (_) {
        // Online hiba → pufferbe esik vissza az egész batch.
      }
    }
    for (final c in contacts) {
      await _db.insertPendingContact({
        'local_uuid': _uuid.v4(),
        'project_id': c.projectId,
        'name': c.name.trim(),
        'address': c.address.trim(),
        'phone': c.phone.trim(),
        'email': c.email.trim(),
        'note': c.note.trim(),
        'created_at': DateTime.now().toIso8601String(),
      });
    }
    return (uploaded: 0, queued: contacts.length);
  }
}

/// Kapcsolat + megjelenítési állapot (pending-e, és ha igen, a helyi kulcs).
class ContactWithState {
  final Contact contact;
  final bool isPending;
  final String? localUuid;
  const ContactWithState({
    required this.contact,
    required this.isPending,
    this.localUuid,
  });
}

class ContactOfflineException implements Exception {
  final String message;
  ContactOfflineException(this.message);
  @override
  String toString() => message;
}
