import 'dart:convert';

import 'package:flutter/foundation.dart';
import 'package:http/http.dart' as http;
import 'package:shared_preferences/shared_preferences.dart';

import '../config/api_config.dart';
import '../models/app_user.dart';

class AuthException implements Exception {
  const AuthException(this.message, {this.errors = const {}});

  final String message;
  final Map<String, List<String>> errors;

  @override
  String toString() => message;
}

class AuthService {
  static const String _loggedInKey = 'auth_logged_in';
  static const String _tokenKey = 'auth_access_token';
  static const String _nameKey = 'auth_user_name';
  static const String _emailKey = 'auth_user_email';

  Future<String?> get savedName async {
    final preferences = await SharedPreferences.getInstance();
    return _normalizeName(preferences.getString(_nameKey));
  }

  Future<AppUser> register({
    required String name,
    required String email,
    required String password,
    required String passwordConfirmation,
  }) async {
    if (password != passwordConfirmation) {
      throw const AuthException('Konfirmasi password tidak sesuai.');
    }

    try {
      final response = await _post('/register', body: {
        'name': name.trim(),
        'email': email.trim(),
        'password': password,
        'password_confirmation': passwordConfirmation,
      });
      final payload = _decodeResponse(response);
      _throwIfUnsuccessful(response, payload);

      final responseUser = _userFromPayload(payload);
      final user = responseUser.copyWith(
        name: _normalizeName(responseUser.name),
        email: responseUser.email.isEmpty ? email.trim() : responseUser.email,
      );
      await _saveUser(user, loggedIn: false);

      return user;
    } on AuthException {
      rethrow;
    } catch (_) {
      throw const AuthException(
        'Tidak bisa terhubung ke server. Periksa koneksi dan alamat API.',
      );
    }
  }

  Future<AppUser> login({
    required String email,
    required String password,
  }) async {
    try {
      final response = await _post('/login', body: {
        'email': email.trim(),
        'password': password,
      });
      final payload = _decodeResponse(response);
      _throwIfUnsuccessful(response, payload);

      final user = _userFromPayload(payload);
      final token = _stringAt(payload, ['data', 'token']);

      if (token.isEmpty) {
        throw const AuthException('Token login tidak ditemukan dari server.');
      }

      await _saveUser(user, token: token, loggedIn: true);
      return user;
    } on AuthException {
      rethrow;
    } catch (_) {
      throw const AuthException(
        'Tidak bisa terhubung ke server. Periksa koneksi dan alamat API.',
      );
    }
  }

  Future<AppUser?> profile() async {
    final preferences = await SharedPreferences.getInstance();
    final loggedIn = preferences.getBool(_loggedInKey) ?? false;
    if (!loggedIn) return null;

    final token = preferences.getString(_tokenKey);
    final email = preferences.getString(_emailKey);
    final fallbackUser = email == null
        ? null
        : AppUser(
            id: 0,
            name: _normalizeName(preferences.getString(_nameKey)),
            email: email,
          );

    if (token == null || token.isEmpty) return fallbackUser;

    try {
      final response = await http
          .get(
            ApiConfig.apiUri('/profile'),
            headers: _headers(token: token),
          )
          .timeout(const Duration(seconds: 10));
      final payload = _decodeResponse(response);
      _throwIfUnsuccessful(response, payload);

      final user = _userFromPayload(payload);
      await _saveUser(user, token: token, loggedIn: true);
      return user;
    } on AuthException {
      rethrow;
    } catch (_) {
      return fallbackUser;
    }
  }

  Future<void> logout() async {
    final preferences = await SharedPreferences.getInstance();
    final token = preferences.getString(_tokenKey);

    if (token != null && token.isNotEmpty) {
      try {
        await _post('/logout', token: token);
      } catch (_) {
        // Session lokal tetap dibersihkan meski server logout gagal.
      }
    }

    await preferences.setBool(_loggedInKey, false);
    await preferences.remove(_tokenKey);
  }

  Future<void> clearSession() async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.setBool(_loggedInKey, false);
    await preferences.remove(_tokenKey);
  }

  Future<bool> checkApiHealth() async => true;
  Future<bool> checkLaravelApiHealth() async {
    try {
      final response = await http
          .get(ApiConfig.apiUri('/health'))
          .timeout(const Duration(seconds: 3));
      return response.statusCode >= 200 && response.statusCode < 500;
    } catch (_) {
      return false;
    }
  }

  Future<void> _saveUser(
    AppUser user, {
    String? token,
    required bool loggedIn,
  }) async {
    final preferences = await SharedPreferences.getInstance();
    await preferences.setBool(_loggedInKey, loggedIn);
    await preferences.setString(_nameKey, _normalizeName(user.name));
    await preferences.setString(_emailKey, user.email);
    if (token != null && token.isNotEmpty) {
      await preferences.setString(_tokenKey, token);
    }
  }

  Future<http.Response> _post(
    String path, {
    Map<String, dynamic>? body,
    String? token,
  }) async {
    final url = ApiConfig.apiUri(path);
    final isLoginRequest = path == '/login' || path == 'login';

    if (isLoginRequest) {
      debugPrint('LOGIN REQUEST METHOD: POST');
      debugPrint('LOGIN REQUEST URL: $url');
    }

    try {
      final response = await http
          .post(
            url,
            headers: _headers(token: token),
            body: jsonEncode(body ?? const <String, dynamic>{}),
          )
          .timeout(const Duration(seconds: 15));

      if (isLoginRequest) {
        debugPrint('LOGIN RESPONSE STATUS: ${response.statusCode}');
        debugPrint('LOGIN RESPONSE BODY: ${response.body}');
      }

      return response;
    } catch (error) {
      if (isLoginRequest) {
        debugPrint('LOGIN REQUEST ERROR: $error');
      }
      rethrow;
    }
  }

  Map<String, String> _headers({String? token}) {
    return {
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      if (token != null && token.isNotEmpty) 'Authorization': 'Bearer $token',
    };
  }

  Map<String, dynamic> _decodeResponse(http.Response response) {
    if (response.body.trim().isEmpty) return <String, dynamic>{};

    final decoded = jsonDecode(response.body);
    if (decoded is Map<String, dynamic>) return decoded;

    throw const AuthException('Format response server tidak valid.');
  }

  void _throwIfUnsuccessful(
    http.Response response,
    Map<String, dynamic> payload,
  ) {
    if (response.statusCode >= 200 && response.statusCode < 300) return;

    throw AuthException(
      _messageFromPayload(payload),
      errors: _errorsFromPayload(payload),
    );
  }

  AppUser _userFromPayload(Map<String, dynamic> payload) {
    final data = payload['data'];
    final userJson = data is Map<String, dynamic> ? data['user'] : null;

    if (userJson is! Map<String, dynamic>) {
      throw const AuthException('Data user tidak ditemukan dari server.');
    }

    final user = AppUser.fromJson(userJson);
    return user.copyWith(name: _normalizeName(user.name));
  }

  String _messageFromPayload(Map<String, dynamic> payload) {
    final errors = _errorsFromPayload(payload);
    if (errors.isNotEmpty) {
      return _localizeAuthMessage(errors.values.first.first);
    }

    final message = payload['message']?.toString().trim();
    if (message != null && message.isNotEmpty && message != 'Success') {
      return _localizeAuthMessage(message);
    }

    return 'Permintaan gagal diproses. Silakan coba lagi.';
  }

  Map<String, List<String>> _errorsFromPayload(Map<String, dynamic> payload) {
    final errors = payload['errors'];
    if (errors is! Map) return const {};

    return errors.map((key, value) {
      final messages = value is List
          ? value
              .map((message) => _localizeAuthMessage(message.toString()))
              .toList()
          : <String>[_localizeAuthMessage(value.toString())];
      return MapEntry(key.toString(), messages);
    });
  }

  String _localizeAuthMessage(String message) {
    final text = message.trim();
    final normalized = text.toLowerCase();

    if (normalized.contains('invalid credentials') ||
        normalized.contains('incorrect password') ||
        normalized.contains('wrong password') ||
        normalized.contains('password is incorrect') ||
        normalized.contains('these credentials do not match') ||
        normalized.contains('the provided credentials are incorrect')) {
      return 'Email atau password yang Anda masukkan salah.';
    }

    if (normalized.contains('user not found') ||
        normalized.contains('email not found') ||
        normalized.contains('no user found') ||
        normalized.contains('account not found')) {
      return 'Akun dengan email tersebut tidak ditemukan.';
    }

    if (normalized.contains('unauthenticated') ||
        normalized.contains('unauthorized')) {
      return 'Sesi Anda tidak valid. Silakan login kembali.';
    }

    if (normalized.contains('too many login attempts') ||
        normalized.contains('too many attempts')) {
      return 'Terlalu banyak percobaan login. Silakan coba lagi nanti.';
    }

    if (normalized.contains('email field is required') ||
        normalized.contains('email is required')) {
      return 'Email wajib diisi.';
    }

    if (normalized.contains('password field is required') ||
        normalized.contains('password is required')) {
      return 'Password wajib diisi.';
    }

    return text;
  }

  String _stringAt(Map<String, dynamic> payload, List<String> path) {
    Object? current = payload;
    for (final key in path) {
      if (current is! Map<String, dynamic>) return '';
      current = current[key];
    }
    return current?.toString().trim() ?? '';
  }

  String _normalizeName(String? value) {
    final name = value?.trim() ?? '';
    return name.isEmpty ? 'Wisatawan' : name;
  }
}
