import 'dart:convert';

import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:http/http.dart' as http;

import 'instance.dart';

/// The shell's credential, and the only thing it stores.
///
/// One long-lived Sanctum token carrying exactly `portal:session`, kept in the
/// Keychain or EncryptedSharedPreferences. It is exchanged for an ordinary web
/// session on launch and is never handed to JavaScript — not to localStorage,
/// not to a variable, not to a query string. Native code holds it and native
/// code sends it.
class Session {
  static const _storage = FlutterSecureStorage(
    aOptions: AndroidOptions(encryptedSharedPreferences: true),
    iOptions: IOSOptions(accessibility: KeychainAccessibility.first_unlock),
  );

  static const _key = 'portal_token';

  Future<String?> token() => _storage.read(key: _key);

  Future<bool> get hasToken async => (await token()) != null;

  /// First launch only. After this the token is all the shell needs.
  Future<({bool ok, String? error})> signIn(String identifier, String password, String deviceName) async {
    final response = await http.post(
      Instance.tokenEndpoint,
      headers: const {'Accept': 'application/json', 'Content-Type': 'application/json'},
      body: jsonEncode({
        'identifier': identifier,
        'password': password,
        'device_name': deviceName,
      }),
    );

    if (response.statusCode != 200) {
      final body = _decode(response.body);

      return (ok: false, error: body['error'] as String? ?? 'تعذّر تسجيل الدخول');
    }

    final body = _decode(response.body);
    await _storage.write(key: _key, value: body['token'] as String);

    return (ok: true, error: null);
  }

  /// Remote sign-out: the server drops the token, then the local copy goes.
  Future<void> signOut() async {
    final stored = await token();

    if (stored != null) {
      try {
        await http.post(
          Instance.revokeEndpoint,
          headers: {'Accept': 'application/json', 'Authorization': 'Bearer $stored'},
        );
      } catch (_) {
        // A phone with no signal still gets to sign out locally.
      }
    }

    await _storage.delete(key: _key);
  }

  /// Register this device's FCM token against the account.
  ///
  /// Sent with the bearer token rather than from the page, so the page never
  /// needs to know either credential.
  Future<void> registerPushToken(String fcmToken, String platform, String? deviceName) async {
    final stored = await token();

    if (stored == null) return;

    try {
      await http.post(
        Instance.pushTokenEndpoint,
        headers: {
          'Accept': 'application/json',
          'Content-Type': 'application/json',
          'Authorization': 'Bearer $stored',
        },
        body: jsonEncode({
          'token': fcmToken,
          'platform': platform,
          'device_name': deviceName,
        }),
      );
    } catch (_) {
      // Push registration is a best effort; the portal still works without it.
    }
  }

  Map<String, dynamic> _decode(String body) {
    try {
      return jsonDecode(body) as Map<String, dynamic>;
    } catch (_) {
      return const {};
    }
  }
}
