/// Frappe Authentication Service
///
/// Handles Frappe/ERPNext session management (sid and csrf_token).
/// Provides methods to get session, login, and manage session storage.
library;

import 'package:dio/dio.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:worker/core/constants/api_constants.dart';
import 'package:worker/core/models/frappe_session_model.dart';

/// Frappe Auth Service
///
/// Manages Frappe session lifecycle:
/// 1. Get initial session (public API)
/// 2. Login with phone number
/// 3. Store sid and csrf_token in secure storage
/// 4. Provide session data for API requests
class FrappeAuthService {
  FrappeAuthService(this._dio, this._secureStorage);

  final Dio _dio;
  final FlutterSecureStorage _secureStorage;

  /// Storage keys for Frappe session
  static const String _keyFrappeSid = 'frappe_sid';
  static const String _keyFrappeCsrfToken = 'frappe_csrf_token';
  static const String _keyFrappeFullName = 'frappe_full_name';
  static const String _keyFrappeUserId = 'frappe_user_id';

  /// Get Frappe session from API
  ///
  /// This endpoint doesn't require authentication - it's public.
  /// Returns initial session for subsequent API calls.
  ///
  /// API: POST /api/method/dbiz_common.dbiz_common.api.auth.get_session
  Future<FrappeSessionResponse> getSession() async {
    try {
      final url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeGetSession}';

      final response = await _dio.post<Map<String, dynamic>>(
        url,
        data: '', // Empty data as per docs
      );

      if (response.data == null) {
        throw Exception('Empty response from Frappe session API');
      }

      final sessionResponse = FrappeSessionResponse.fromJson(response.data!);

      // Store session in secure storage
      await _storeSession(
        sid: sessionResponse.sid,
        csrfToken: sessionResponse.csrfToken,
        fullName: sessionResponse.fullName,
        userId: ApiConstants.frappePublicUserId,
      );

      return sessionResponse;
    } on DioException catch (e) {
      throw Exception('Failed to get Frappe session: ${e.message}');
    } catch (e) {
      throw Exception('Unexpected error getting Frappe session: $e');
    }
  }

  /// Login with phone number
  ///
  /// Requires existing session (sid and csrf_token).
  /// Returns new session with user's authentication.
  ///
  /// API: POST /api/method/building_material.building_material.api.auth.login
  /// Headers: Cookie (with sid), X-Frappe-Csrf-Token
  /// Body: { "username": "phone", "googleid": null, "facebookid": null, "zaloid": null }
  ///
  /// Note: Password not used yet, but field reserved for future use
  Future<FrappeSessionResponse> login(String phone, {String? password}) async {
    try {
      // Ensure we have a session first
      final session = await getStoredSession();
      if (session == null) {
        await getSession();
        final newSession = await getStoredSession();
        if (newSession == null) {
          throw Exception('Failed to initialize session');
        }
      }

      const url = '${ApiConstants.baseUrl}${ApiConstants.frappeApiMethod}${ApiConstants.frappeLogin}';

      // Get stored session - only need sid and csrf_token
      final storedSession = await getStoredSession();

      final response = await _dio.post<Map<String, dynamic>>(
        url,
        data: {
          'username': phone,
          'googleid': null,
          'facebookid': null,
          'zaloid': null,
          // Password field reserved for future use
          // 'password': password,
        },
        options: Options(
          headers: {
            'Cookie': 'sid=${storedSession!['sid']!}',
            'X-Frappe-Csrf-Token': storedSession['csrfToken']!,
            'Content-Type': 'application/json',
          },
        ),
      );

      if (response.data == null) {
        throw Exception('Empty response from login API');
      }

      final loginResponse = FrappeSessionResponse.fromJson(response.data!);

      // Store new session after login
      await _storeSession(
        sid: loginResponse.sid,
        csrfToken: loginResponse.csrfToken,
        fullName: loginResponse.fullName,
        userId: phone, // Use phone as userId after login
      );

      return loginResponse;
    } on DioException catch (e) {
      throw Exception('Login failed: ${e.message}');
    } catch (e) {
      throw Exception('Unexpected error during login: $e');
    }
  }

  /// Store session in secure storage
  Future<void> _storeSession({
    required String sid,
    required String csrfToken,
    required String fullName,
    required String userId,
  }) async {
    await Future.wait([
      _secureStorage.write(key: _keyFrappeSid, value: sid),
      _secureStorage.write(key: _keyFrappeCsrfToken, value: csrfToken),
      _secureStorage.write(key: _keyFrappeFullName, value: fullName),
      _secureStorage.write(key: _keyFrappeUserId, value: userId),
    ]);
  }

  /// Get stored session from secure storage
  Future<Map<String, String>?> getStoredSession() async {
    final results = await Future.wait([
      _secureStorage.read(key: _keyFrappeSid),
      _secureStorage.read(key: _keyFrappeCsrfToken),
      _secureStorage.read(key: _keyFrappeFullName),
      _secureStorage.read(key: _keyFrappeUserId),
    ]);

    final sid = results[0];
    final csrfToken = results[1];
    final fullName = results[2];
    final userId = results[3];

    // Return null if session is incomplete
    if (sid == null || csrfToken == null) {
      return null;
    }

    return {
      'sid': sid,
      'csrfToken': csrfToken,
      'fullName': fullName ?? 'User',
      'userId': userId ?? ApiConstants.frappePublicUserId,
    };
  }

  /// Ensure valid session exists, fetch new one if needed
  Future<Map<String, String>> ensureSession() async {
    var session = await getStoredSession();

    if (session == null) {
      // No session in storage, get a new one
      await getSession();
      session = await getStoredSession();
      if (session == null) {
        throw Exception('Failed to get session');
      }
    }

    return session;
  }

  /// Check if session exists in storage
  Future<bool> hasSession() async {
    final sid = await _secureStorage.read(key: _keyFrappeSid);
    final csrfToken = await _secureStorage.read(key: _keyFrappeCsrfToken);
    return sid != null && csrfToken != null;
  }

  /// Get headers for Frappe API requests
  /// Only sends sid in Cookie - other fields are not needed
  Future<Map<String, String>> getHeaders() async {
    final session = await ensureSession();

    return {
      'Cookie': 'sid=${session['sid']!}',
      'X-Frappe-Csrf-Token': session['csrfToken']!,
      'Content-Type': 'application/json',
    };
  }

  /// Clear stored session
  Future<void> clearSession() async {
    await Future.wait([
      _secureStorage.delete(key: _keyFrappeSid),
      _secureStorage.delete(key: _keyFrappeCsrfToken),
      _secureStorage.delete(key: _keyFrappeFullName),
      _secureStorage.delete(key: _keyFrappeUserId),
    ]);
  }
}
