/// Base exception class for all application exceptions
abstract class AppException implements Exception {
  final String message;
  final String? details;
  final StackTrace? stackTrace;

  AppException(this.message, [this.details, this.stackTrace]);

  @override
  String toString() {
    if (details != null) {
      return '$runtimeType: $message\nDetails: $details';
    }
    return '$runtimeType: $message';
  }
}

/// Exception thrown when network operations fail
class NetworkException extends AppException {
  final int? statusCode;
  final String? url;

  NetworkException(
    super.message, [
    super.details,
    super.stackTrace,
    this.statusCode,
    this.url,
  ]);

  @override
  String toString() {
    final buffer = StringBuffer('NetworkException: $message');
    if (statusCode != null) {
      buffer.write('\nStatus Code: $statusCode');
    }
    if (url != null) {
      buffer.write('\nURL: $url');
    }
    if (details != null) {
      buffer.write('\nDetails: $details');
    }
    return buffer.toString();
  }
}

/// Exception thrown when authentication fails
class AuthenticationException extends AppException {
  final String? userId;
  final AuthenticationFailureReason? reason;

  AuthenticationException(
    super.message, [
    super.details,
    super.stackTrace,
    this.userId,
    this.reason,
  ]);

  @override
  String toString() {
    final buffer = StringBuffer('AuthenticationException: $message');
    if (userId != null) {
      buffer.write('\nUser ID: $userId');
    }
    if (reason != null) {
      buffer.write('\nReason: ${reason!.name}');
    }
    if (details != null) {
      buffer.write('\nDetails: $details');
    }
    return buffer.toString();
  }
}

/// Reasons for authentication failure
enum AuthenticationFailureReason {
  invalidCredentials,
  sessionExpired,
  networkError,
  serverError,
  unknown,
}

/// Exception thrown when parsing fails
class ParseException extends AppException {
  final String? source;
  final String? expectedFormat;

  ParseException(
    super.message, [
    super.details,
    super.stackTrace,
    this.source,
    this.expectedFormat,
  ]);

  @override
  String toString() {
    final buffer = StringBuffer('ParseException: $message');
    if (expectedFormat != null) {
      buffer.write('\nExpected Format: $expectedFormat');
    }
    if (source != null && source!.length <= 100) {
      buffer.write('\nSource: $source');
    } else if (source != null) {
      buffer.write('\nSource: ${source!.substring(0, 100)}...');
    }
    if (details != null) {
      buffer.write('\nDetails: $details');
    }
    return buffer.toString();
  }
}

/// Exception thrown when validation fails
class ValidationException extends AppException {
  final String? fieldName;
  final dynamic invalidValue;
  final List<String>? validationRules;

  ValidationException(
    super.message, [
    super.details,
    super.stackTrace,
    this.fieldName,
    this.invalidValue,
    this.validationRules,
  ]);

  @override
  String toString() {
    final buffer = StringBuffer('ValidationException: $message');
    if (fieldName != null) {
      buffer.write('\nField: $fieldName');
    }
    if (invalidValue != null) {
      buffer.write('\nInvalid Value: $invalidValue');
    }
    if (validationRules != null && validationRules!.isNotEmpty) {
      buffer.write('\nValidation Rules: ${validationRules!.join(", ")}');
    }
    if (details != null) {
      buffer.write('\nDetails: $details');
    }
    return buffer.toString();
  }
}
