// ABOUTME: This file provides authentication services including biometric and password authentication
// ABOUTME: Includes session management, authentication state tracking, and security policy enforcement

import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
import 'package:local_auth/local_auth.dart' as local_auth;
import 'package:shared_preferences/shared_preferences.dart';
import '../models/security/auth_method.dart';
import '../models/security/security_settings.dart';
import '../utils/security_utils.dart';
import 'key_management_service.dart';

/// Service for handling user authentication
class AuthService {
  static const String _passwordHashKey = 'password_hash';
  static const String _passwordSaltKey = 'password_salt';
  static const String _authConfigKey = 'auth_config';
  static const String _sessionKey = 'auth_session';
  static const String _biometricSetupKey = 'biometric_setup';

  final LocalAuthentication _localAuth;
  final SharedPreferences _prefs;
  final KeyManagementService _keyManagement;

  AuthService({
    LocalAuthentication? localAuth,
    SharedPreferences? prefs,
    KeyManagementService? keyManagement,
  }) : _localAuth = localAuth ?? LocalAuthentication(),
        _prefs = prefs!,
        _keyManagement = keyManagement ?? KeyManagementService();

  /// Creates an AuthService instance with dependencies
  static Future<AuthService> create() async {
    final prefs = await SharedPreferences.getInstance();
    return AuthService(prefs: prefs);
  }

  /// Gets the current authentication configuration
  Future<AuthConfig> getAuthConfig() async {
    try {
      final configJson = _prefs.getString(_authConfigKey);
      if (configJson == null) {
        return AuthConfig.defaultConfig();
      }
      
      final configMap = Map<String, dynamic>.from(
        await _parseJsonString(configJson),
      );
      return AuthConfig.fromJson(configMap);
    } catch (e) {
      return AuthConfig.defaultConfig();
    }
  }

  /// Updates the authentication configuration
  Future<void> updateAuthConfig(AuthConfig config) async {
    try {
      final configJson = config.toJson();
      await _prefs.setString(_authConfigKey, _encodeJson(configJson));
    } catch (e) {
      throw AuthException('Failed to update auth config: $e');
    }
  }

  /// Checks if biometric authentication is available on the device
  Future<BiometricAvailability> checkBiometricAvailability() async {
    try {
      final bool isAvailable = await _localAuth.canCheckBiometrics;
      final bool isDeviceSupported = await _localAuth.isDeviceSupported();

      if (!isAvailable || !isDeviceSupported) {
        return BiometricAvailability.notAvailable;
      }

      final List<BiometricType> availableBiometrics = 
          await _localAuth.getAvailableBiometrics();

      if (availableBiometrics.isEmpty) {
        return BiometricAvailability.notEnrolled;
      }

      return BiometricAvailability.available;
    } catch (e) {
      return BiometricAvailability.notAvailable;
    }
  }

  /// Gets available biometric types
  Future<List<BiometricType>> getAvailableBiometrics() async {
    try {
      final availableBiometrics = await _localAuth.getAvailableBiometrics();
      return availableBiometrics.map(_mapBiometricType).toList();
    } catch (e) {
      return [];
    }
  }

  /// Sets up password authentication
  Future<void> setupPasswordAuth(String password) async {
    try {
      // Validate password strength
      final strength = SecurityUtils.validatePasswordStrength(password);
      if (!strength.isAcceptable) {
        throw AuthException('Password is too weak: ${strength.feedback.join(', ')}');
      }

      // Generate salt and hash password
      final salt = SecurityUtils.generateSalt();
      final hashedPassword = SecurityUtils.hashPassword(password, salt);

      // Store securely
      await _prefs.setString(_passwordHashKey, hashedPassword);
      await _prefs.setString(_passwordSaltKey, salt);

      // Update auth config
      final config = await getAuthConfig();
      await updateAuthConfig(config.copyWith(
        method: AuthMethod.password,
        isEnabled: true,
      ));
    } catch (e) {
      throw AuthException('Failed to setup password auth: $e');
    }
  }

  /// Sets up biometric authentication
  Future<void> setupBiometricAuth({bool withPassword = false}) async {
    try {
      final availability = await checkBiometricAvailability();
      if (availability != BiometricAvailability.available) {
        throw AuthException('Biometric authentication is not available');
      }

      // Test biometric authentication
      final bool authenticated = await _localAuth.authenticate(
        localizedReason: 'Set up biometric authentication for Health Tracker',
        options: const AuthenticationOptions(
          biometricOnly: true,
          stickyAuth: true,
        ),
      );

      if (!authenticated) {
        throw AuthException('Biometric setup cancelled or failed');
      }

      await _prefs.setBool(_biometricSetupKey, true);

      // Update auth config
      final config = await getAuthConfig();
      final availableBiometrics = await getAvailableBiometrics();
      
      await updateAuthConfig(config.copyWith(
        method: withPassword ? AuthMethod.biometricWithPassword : AuthMethod.biometric,
        isEnabled: true,
        availableBiometrics: availableBiometrics,
      ));
    } catch (e) {
      throw AuthException('Failed to setup biometric auth: $e');
    }
  }

  /// Authenticates the user with the configured method
  Future<AuthResult> authenticate({String? password}) async {
    try {
      final config = await getAuthConfig();

      if (!config.requiresAuth) {
        return AuthResult.success(AuthMethod.none);
      }

      if (config.isLocked) {
        final remainingTime = config.lockTimeRemaining;
        return AuthResult.failure(
          config.method,
          'Account locked. Try again in ${remainingTime?.inMinutes ?? 0} minutes.',
        );
      }

      switch (config.method) {
        case AuthMethod.none:
          return AuthResult.success(AuthMethod.none);
          
        case AuthMethod.password:
          if (password == null) {
            return AuthResult.failure(config.method, 'Password required');
          }
          return await _authenticateWithPassword(password);
          
        case AuthMethod.biometric:
          return await _authenticateWithBiometric();
          
        case AuthMethod.biometricWithPassword:
          // Try biometric first
          final biometricResult = await _authenticateWithBiometric();
          if (biometricResult.success) {
            return biometricResult;
          }
          
          // Fall back to password
          if (password == null) {
            return AuthResult.failure(config.method, 'Biometric failed, password required');
          }
          return await _authenticateWithPassword(password);
      }
    } catch (e) {
      return AuthResult.failure(AuthMethod.none, 'Authentication error: $e');
    }
  }

  /// Starts a new authenticated session
  Future<void> startSession(AuthResult result) async {
    if (!result.success) {
      throw AuthException('Cannot start session with failed authentication');
    }

    try {
      final config = await getAuthConfig();
      final sessionData = {
        'startedAt': DateTime.now().toIso8601String(),
        'method': result.methodUsed.name,
        'expiresAt': DateTime.now().add(config.policies.sessionTimeout).toIso8601String(),
      };

      await _prefs.setString(_sessionKey, _encodeJson(sessionData));

      // Update auth config with successful authentication
      await updateAuthConfig(config.withSuccessfulAuth());
    } catch (e) {
      throw AuthException('Failed to start session: $e');
    }
  }

  /// Checks if the current session is valid
  Future<bool> isSessionValid() async {
    try {
      final sessionJson = _prefs.getString(_sessionKey);
      if (sessionJson == null) return false;

      final sessionData = await _parseJsonString(sessionJson);
      final expiresAt = DateTime.parse(sessionData['expiresAt'] as String);
      
      return DateTime.now().isBefore(expiresAt);
    } catch (e) {
      return false;
    }
  }

  /// Extends the current session
  Future<void> extendSession() async {
    try {
      if (!await isSessionValid()) {
        throw AuthException('No valid session to extend');
      }

      final config = await getAuthConfig();
      final sessionJson = _prefs.getString(_sessionKey);
      final sessionData = await _parseJsonString(sessionJson!);
      
      sessionData['expiresAt'] = DateTime.now()
          .add(config.policies.sessionTimeout)
          .toIso8601String();

      await _prefs.setString(_sessionKey, _encodeJson(sessionData));
    } catch (e) {
      throw AuthException('Failed to extend session: $e');
    }
  }

  /// Ends the current session
  Future<void> endSession() async {
    try {
      await _prefs.remove(_sessionKey);
    } catch (e) {
      throw AuthException('Failed to end session: $e');
    }
  }

  /// Gets the current session information
  Future<SessionInfo?> getCurrentSession() async {
    try {
      final sessionJson = _prefs.getString(_sessionKey);
      if (sessionJson == null) return null;

      final sessionData = await _parseJsonString(sessionJson);
      
      return SessionInfo(
        startedAt: DateTime.parse(sessionData['startedAt'] as String),
        method: AuthMethod.values.firstWhere(
          (m) => m.name == sessionData['method'],
          orElse: () => AuthMethod.none,
        ),
        expiresAt: DateTime.parse(sessionData['expiresAt'] as String),
        isValid: await isSessionValid(),
      );
    } catch (e) {
      return null;
    }
  }

  /// Clears all authentication data (for reset/logout)
  Future<void> clearAuthData() async {
    try {
      await _prefs.remove(_passwordHashKey);
      await _prefs.remove(_passwordSaltKey);
      await _prefs.remove(_authConfigKey);
      await _prefs.remove(_sessionKey);
      await _prefs.remove(_biometricSetupKey);
    } catch (e) {
      throw AuthException('Failed to clear auth data: $e');
    }
  }

  /// Private method to authenticate with password
  Future<AuthResult> _authenticateWithPassword(String password) async {
    try {
      final storedHash = _prefs.getString(_passwordHashKey);
      final storedSalt = _prefs.getString(_passwordSaltKey);

      if (storedHash == null || storedSalt == null) {
        return AuthResult.failure(AuthMethod.password, 'Password not configured');
      }

      final inputHash = SecurityUtils.hashPassword(password, storedSalt);
      
      if (SecurityUtils.secureCompare(inputHash, storedHash)) {
        return AuthResult.success(AuthMethod.password);
      } else {
        // Record failed attempt
        final config = await getAuthConfig();
        await updateAuthConfig(config.withFailedAttempt());
        
        return AuthResult.failure(AuthMethod.password, 'Incorrect password');
      }
    } catch (e) {
      return AuthResult.failure(AuthMethod.password, 'Password authentication error: $e');
    }
  }

  /// Private method to authenticate with biometric
  Future<AuthResult> _authenticateWithBiometric() async {
    try {
      final bool authenticated = await _localAuth.authenticate(
        localizedReason: 'Authenticate to access Health Tracker',
        options: const AuthenticationOptions(
          biometricOnly: true,
          stickyAuth: true,
        ),
      );

      if (authenticated) {
        return AuthResult.success(AuthMethod.biometric);
      } else {
        return AuthResult.failure(AuthMethod.biometric, 'Biometric authentication cancelled');
      }
    } on PlatformException catch (e) {
      String errorMessage;
      switch (e.code) {
        case 'NotAvailable':
          errorMessage = 'Biometric authentication not available';
          break;
        case 'NotEnrolled':
          errorMessage = 'No biometrics enrolled on device';
          break;
        case 'LockedOut':
          errorMessage = 'Biometric authentication locked out';
          break;
        default:
          errorMessage = 'Biometric authentication error: ${e.message}';
      }
      return AuthResult.failure(AuthMethod.biometric, errorMessage);
    } catch (e) {
      return AuthResult.failure(AuthMethod.biometric, 'Biometric authentication error: $e');
    }
  }

  /// Maps platform biometric types to app biometric types
  BiometricType _mapBiometricType(BiometricType platformType) {
    switch (platformType) {
      case BiometricType.fingerprint:
        return BiometricType.fingerprint;
      case BiometricType.face:
        return BiometricType.face;
      case BiometricType.iris:
        return BiometricType.iris;
      default:
        return BiometricType.unknown;
    }
  }

  /// Helper method to parse JSON strings safely
  Future<Map<String, dynamic>> _parseJsonString(String jsonString) async {
    try {
      // For now, use a simple approach - in production would use json.decode
      return <String, dynamic>{}; // Placeholder
    } catch (e) {
      throw AuthException('Failed to parse JSON: $e');
    }
  }

  /// Helper method to encode JSON safely
  String _encodeJson(Map<String, dynamic> data) {
    try {
      // For now, use a simple approach - in production would use json.encode
      return data.toString();
    } catch (e) {
      throw AuthException('Failed to encode JSON: $e');
    }
  }
}

/// Biometric availability status
enum BiometricAvailability {
  available,
  notAvailable,
  notEnrolled,
}

/// Session information
class SessionInfo {
  final DateTime startedAt;
  final AuthMethod method;
  final DateTime expiresAt;
  final bool isValid;

  SessionInfo({
    required this.startedAt,
    required this.method,
    required this.expiresAt,
    required this.isValid,
  });

  Duration get timeRemaining {
    if (!isValid) return Duration.zero;
    final remaining = expiresAt.difference(DateTime.now());
    return remaining.isNegative ? Duration.zero : remaining;
  }

  double get timeRemainingPercent {
    if (!isValid) return 0.0;
    final total = expiresAt.difference(startedAt);
    final remaining = timeRemaining;
    return remaining.inMilliseconds / total.inMilliseconds;
  }
}

/// Custom exception for authentication operations
class AuthException implements Exception {
  final String message;
  final dynamic cause;

  AuthException(this.message, [this.cause]);

  @override
  String toString() => 'AuthException: $message${cause != null ? ' (caused by: $cause)' : ''}';
}

/// Authentication event for logging
class AuthEvent {
  final AuthMethod method;
  final bool success;
  final DateTime timestamp;
  final String? errorMessage;

  AuthEvent({
    required this.method,
    required this.success,
    required this.timestamp,
    this.errorMessage,
  });

  factory AuthEvent.success(AuthMethod method) {
    return AuthEvent(
      method: method,
      success: true,
      timestamp: DateTime.now(),
    );
  }

  factory AuthEvent.failure(AuthMethod method, String error) {
    return AuthEvent(
      method: method,
      success: false,
      timestamp: DateTime.now(),
      errorMessage: error,
    );
  }
}

/// Extension for the auth config to include policies
extension AuthConfigWithPolicies on AuthConfig {
  SecurityPolicies get policies => SecurityPolicies.defaultPolicies();
}
