import 'dart:async';

import 'package:pocketbase/pocketbase.dart';

import 'package:listenmeister/models/models.dart';

class ApiService {
  final PocketBase pb;

  ApiService(this.pb);

  String? get userId => pb.authStore.record?.id;

  Future<void> login(String email, String password) async {
    await pb.collection('users').authWithPassword(email, password);
  }

  Future<void> register(String email, String password) async {
    await pb
        .collection('users')
        .create(
          body: {
            'email': email,
            'password': password,
            'passwordConfirm': password,
            'emailVisibility': true,
          },
        );
    await login(email, password);
  }

  void logout() {
    pb.authStore.clear();
  }

  Stream<void> watchLists() {
    if (userId == null) return Stream.value(null);

    late final StreamController<void> controller;
    Future<void> Function()? unsubscribe;

    controller = StreamController<void>(
      onListen: () async {
        unsubscribe = await pb.collection('lists').subscribe('*', (e) {
          if (!controller.isClosed) {
            controller.add(null);
          }
        }, filter: 'owner = "$userId" || members ?~ "$userId"');
      },
      onCancel: () {
        unsubscribe?.call();
      },
    );

    return controller.stream;
  }

  Future<List<Liste>> getLists() async {
    if (userId == null) return [];
    final List<RecordModel> res = await pb
        .collection('lists')
        .getFullList(filter: 'owner = "$userId" || members ?~ "$userId"');
    return res.map((r) => Liste.fromJson(r.toJson())).toList();
  }

  Future<Liste> createList(String title) async {
    if (userId == null) throw Exception('Nicht eingeloggt');
    final RecordModel rec = await pb
        .collection('lists')
        .create(body: {'title': title, 'owner': userId});
    return Liste.fromJson(rec.toJson());
  }

  Future<void> updateList(
    String listId, {
    String? title,
    List<String>? members,
  }) async {
    if (title == null && members == null) return;
    final body = <String, dynamic>{};
    if (title != null) {
      body['title'] = title;
    }
    if (members != null) {
      body['members'] = members;
    }
    await pb.collection('lists').update(listId, body: body);
  }

  Future<void> deleteList(String listId) async {
    await pb.collection('lists').delete(listId);
  }

  Stream<void> watchItems(String listId) {
    late final StreamController<void> controller;
    Future<void> Function()? unsubscribe;

    controller = StreamController<void>(
      onListen: () async {
        unsubscribe = await pb.collection('items').subscribe('*', (e) {
          if (!controller.isClosed) {
            controller.add(null);
          }
        }, filter: 'list = "$listId"');
      },
      onCancel: () {
        unsubscribe?.call();
      },
    );

    return controller.stream;
  }

  Future<List<Item>> getItems(String listId) async {
    final List<RecordModel> res = await pb
        .collection('items')
        .getFullList(filter: 'list = "$listId"', sort: 'position');
    return res.map((r) => Item.fromJson(r.toJson())).toList();
  }

  Future<Item> createItem(String listId, String name, int position) async {
    final RecordModel rec = await pb
        .collection('items')
        .create(
          body: {
            'list': listId,
            'name': name,
            'position': position,
            'checked': false,
          },
        );
    return Item.fromJson(rec.toJson());
  }

  Future<void> deleteItem(String itemId) async {
    await pb.collection('items').delete(itemId);
  }

  Future<void> updateItem(String itemId, {bool? checked, String? name}) async {
    if (checked == null && name == null) return;
    final Map<String, dynamic> body = <String, dynamic>{};
    if (checked != null) {
      body['checked'] = checked;
    }
    if (name != null) {
      body['name'] = name;
    }
    await pb.collection('items').update(itemId, body: body);
  }

  Future<RecordModel?> findUserByEmail(String email) async {
    try {
      return await pb.collection('users').getFirstListItem('email = "$email"');
    } catch (e) {
      return null;
    }
  }

  Future<List<RecordModel>> getUsersByIds(List<String> ids) async {
    if (ids.isEmpty) return [];
    final String filter = ids.map((id) => 'id = "$id"').join(' || ');
    return await pb.collection('users').getFullList(filter: filter);
  }

  Stream<void> watchStoreLayouts() {
    if (userId == null) return Stream.value(null);

    late final StreamController<void> controller;
    Future<void> Function()? unsubscribe;

    controller = StreamController<void>(
      onListen: () async {
        final String filter = 'owner = "$userId" || isPublic = true';
        unsubscribe = await pb.collection('store_layouts').subscribe('*', (e) {
          if (!controller.isClosed) {
            controller.add(null);
          }
        }, filter: filter);
      },
      onCancel: () => unsubscribe?.call(),
    );

    return controller.stream;
  }

  Stream<void> watchStoreLayout(String layoutId) {
    late final StreamController<void> controller;
    Future<void> Function()? unsubscribe;

    controller = StreamController<void>(
      onListen: () async {
        unsubscribe = await pb.collection('store_layouts').subscribe(layoutId, (
          e,
        ) {
          if (!controller.isClosed) {
            controller.add(null);
          }
        });
      },
      onCancel: () => unsubscribe?.call(),
    );

    return controller.stream;
  }

  Future<List<StoreLayout>> getStoreLayouts({bool includePublic = true}) async {
    if (userId == null) return [];
    final List<String> filterParts = ['owner = "$userId"'];
    if (includePublic) {
      filterParts.add('isPublic = true');
    }
    final String filter = filterParts.join(' || ');
    final List<RecordModel> res = await pb
        .collection('store_layouts')
        .getFullList(filter: filter, sort: '-updated');
    final layouts = res.map((r) => StoreLayout.fromJson(r.toJson())).toList();
    layouts.sort((a, b) {
      final bool aOwn = a.owner == userId;
      final bool bOwn = b.owner == userId;
      if (aOwn == bOwn) {
        return a.name.toLowerCase().compareTo(b.name.toLowerCase());
      }
      return aOwn ? -1 : 1;
    });
    return layouts;
  }

  Future<StoreLayout> createStoreLayout({
    required String name,
    double? latitude,
    double? longitude,
    String? address,
    bool isPublic = false,
  }) async {
    if (userId == null) throw Exception('Nicht eingeloggt');
    final Map<String, dynamic> body = {
      'name': name,
      'owner': userId,
      'isPublic': isPublic,
    };
    if (latitude != null) body['latitude'] = latitude;
    if (longitude != null) body['longitude'] = longitude;
    if (address != null && address.isNotEmpty) body['address'] = address;

    final RecordModel rec = await pb
        .collection('store_layouts')
        .create(body: body);
    return StoreLayout.fromJson(rec.toJson());
  }

  Future<StoreLayout?> getStoreLayoutById(String layoutId) async {
    try {
      final RecordModel rec = await pb
          .collection('store_layouts')
          .getOne(layoutId);
      return StoreLayout.fromJson(rec.toJson());
    } catch (e) {
      return null;
    }
  }

  Future<void> updateStoreLayout(
    String layoutId, {
    String? name,
    double? latitude,
    double? longitude,
    String? address,
    bool? isPublic,
    bool clearLatitude = false,
    bool clearLongitude = false,
    bool clearAddress = false,
  }) async {
    final Map<String, dynamic> body = {};
    if (name != null) body['name'] = name;
    if (latitude != null) body['latitude'] = latitude;
    if (clearLatitude && latitude == null) body['latitude'] = null;
    if (longitude != null) body['longitude'] = longitude;
    if (clearLongitude && longitude == null) body['longitude'] = null;
    if (address != null) body['address'] = address;
    if (clearAddress && address == null) body['address'] = null;
    if (isPublic != null) body['isPublic'] = isPublic;

    if (body.isEmpty) return;

    await pb.collection('store_layouts').update(layoutId, body: body);
  }

  Future<void> deleteStoreLayout(String layoutId) async {
    await pb.collection('store_layouts').delete(layoutId);
  }

  Stream<void> watchStoreSections(String layoutId) {
    late final StreamController<void> controller;
    Future<void> Function()? unsubscribe;

    controller = StreamController<void>(
      onListen: () async {
        unsubscribe = await pb.collection('store_sections').subscribe('*', (e) {
          if (!controller.isClosed) {
            controller.add(null);
          }
        }, filter: 'layout = "$layoutId"');
      },
      onCancel: () => unsubscribe?.call(),
    );

    return controller.stream;
  }

  Future<List<StoreSection>> getStoreSections(String layoutId) async {
    final List<RecordModel> res = await pb
        .collection('store_sections')
        .getFullList(filter: 'layout = "$layoutId"', sort: 'position');
    return res.map((r) => StoreSection.fromJson(r.toJson())).toList();
  }

  Future<StoreSection> createStoreSection({
    required String layoutId,
    required String name,
    int position = 0,
    String? category,
  }) async {
    final RecordModel rec = await pb
        .collection('store_sections')
        .create(
          body: {
            'layout': layoutId,
            'name': name,
            'position': position,
            if (category != null && category.isNotEmpty) 'category': category,
          },
        );
    return StoreSection.fromJson(rec.toJson());
  }

  Future<void> updateStoreSection(
    String sectionId, {
    String? name,
    int? position,
    String? category,
    bool clearCategory = false,
  }) async {
    final Map<String, dynamic> body = {};
    if (name != null) body['name'] = name;
    if (position != null) body['position'] = position;
    if (category != null) body['category'] = category;
    if (clearCategory && category == null) body['category'] = null;
    if (body.isEmpty) return;
    await pb.collection('store_sections').update(sectionId, body: body);
  }

  Future<void> deleteStoreSection(String sectionId) async {
    await pb.collection('store_sections').delete(sectionId);
  }
}
