import 'dart:io';

import 'package:flutter/material.dart';
import 'package:mobile/models/user.dart';
import 'package:mobile/results.dart';
import 'package:mobile/server/server.dart';
import 'package:path_provider/path_provider.dart';

class CookieController {
  CookieController();

  static Future<File> get _cacheFile async {
    final directory = await getApplicationCacheDirectory();
    return File("${directory.path}/cookies.txt").create();
  }

  Future<void> clear() async {
    (await _cacheFile).writeAsString("", mode: FileMode.write);
  }

  Future<void> save(String token) async {
    (await _cacheFile).writeAsString(token, mode: FileMode.write);
  }

  Future<Result<String, Null>> load() async {
    final token = await (await _cacheFile).readAsString();
    if (token.isEmpty) {
      return const Err(null);
    }
    return Ok(token);
  }
}

class SessionController {
  final Server server;
  final CookieController cookieController = CookieController();

  User? _sessionUser;

  final List<_ChangeListener> _sessionChangeListeners = [];
  final List<_ChangeListener> _userChangeListeners = [];

  SessionController({required this.server});

  Future<Result<Null, String>> loginUser(String email, String password) async {
    final loginResult = await server.login(email, password);
    switch (loginResult) {
      case Ok<String, String>(value: final sessionToken):
        await cookieController.save(sessionToken);
        notifySessionChangeListeners();
        return const Ok(null);
      case Err<String, String>(value: final message):
        return Err(message);
    }
  }

  Future<Result<Null, Null>> loadCachedUser() async {
    switch (await cookieController.load()) {
      case Ok<String, Null>():
        return _loadCurrentUser();
      case Err<String, Null>():
        notifyUserChangeListeners();
        return const Err(null);
    }
  }

  Future<Result<Null, Null>> loadUpdatedUser() async {
    return _loadCurrentUser();
  }

  Future<Result<Null, Null>> _loadCurrentUser() async {
    final sessionUserResult = await requestWithSession<User>(
        (server, sessionToken) => server.sessionUser(sessionToken));
    switch (sessionUserResult) {
      case Ok<User, String>(value: final sessionUser):
        _sessionUser = sessionUser;
        notifyUserChangeListeners();
        return const Ok(null);
      case Err<User, String>():
        return const Err(null);
    }
  }

  Future<Null> logout() async {
    switch (await cookieController.load()) {
      case Ok<String, Null>(value: final sessionToken):
        await server.logout(sessionToken);
        await cookieController.clear();
        _sessionUser = null;
        notifySessionChangeListeners();
      case Err<String, Null>():
        notifySessionChangeListeners();
        return;
    }
  }

  User get user {
    final user = _sessionUser;
    if (user == null) {
      throw NoUser();
    }
    return user;
  }

  bool get hasUser {
    return _sessionUser != null;
  }

  Future<Result<Null, String>> addBalance() async {
    final addBalanceResult = await requestWithSession(
        (server, sessionToken) => server.addBalance(sessionToken));
    if (addBalanceResult case Err<Null, String>(value: final message)) {
      return Err(message);
    }
    if (await _loadCurrentUser() case Err<Null, Null>()) {
      return const Err("could not fetch user");
    }
    return const Ok(null);
  }

  /// Package private.
  Future<Result<T, String>> requestWithSession<T>(
      Future<Result<T, String>> Function(Server server, String sessionToken)
          func) async {
    switch (await cookieController.load()) {
      case Err<String, Null>():
        return const Err("unathorized");
      case Ok<String, Null>(value: final sessionToken):
        final result = await func(server, sessionToken);
        if (result case Err<T, String>(value: final message)) {
          if (message == "unauthorized") {
            cookieController.clear();
            _sessionUser = null;
            notifySessionChangeListeners();
            notifyUserChangeListeners();
            return const Err("unathorized");
          }
        }
        return result;
    }
  }

  void notifySessionChangeListeners() {
    for (final listener in _sessionChangeListeners) {
      listener.notify();
    }
  }

  void notifyUserChangeListeners() {
    for (final listener in _userChangeListeners) {
      listener.notify();
    }
  }

  void _addSessionChangeListener(_ChangeListener listener) {
    _sessionChangeListeners.add(listener);
  }

  void _addUserChangeListener(_ChangeListener listener) {
    _userChangeListeners.add(listener);
  }
}

abstract class _ChangeListener {
  void notify();
}

class NoUser implements Exception {}

class SessionProvider extends ChangeNotifier implements _ChangeListener {
  final SessionController controller;

  SessionProvider({required this.controller}) {
    controller._addSessionChangeListener(this);
  }

  @override
  void notify() {
    notifyListeners();
  }
}

class CurrentUserProvider extends ChangeNotifier implements _ChangeListener {
  final SessionController controller;

  CurrentUserProvider({required this.controller}) {
    controller._addUserChangeListener(this);
  }

  @override
  void notify() {
    notifyListeners();
  }
}