import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:xp_models/xp_models.dart';
import '../utils/port_detector.dart';

class ApiService {
  static const String _defaultBaseUrl = 'http://[::1]:8080';
  static const String _defaultWsUrl = 'ws://[::1]:8080/ws';

  final String _baseUrl;
  final String _wsUrl;
  final http.Client _httpClient;
  WebSocketChannel? _wsChannel;

  ApiService({String? baseUrl, String? wsUrl, http.Client? httpClient})
    : _baseUrl = baseUrl ?? _buildDynamicBaseUrl(),
      _wsUrl = wsUrl ?? _buildDynamicWsUrl(),
      _httpClient = httpClient ?? http.Client();

  /// Builds the base URL using dynamic port detection or falls back to default
  static String _buildDynamicBaseUrl() {
    try {
      return PortDetector.buildBaseUrl(
        defaultHost: '[::1]',
        defaultPort: '8080',
        defaultProtocol: 'http',
      );
    } catch (e) {
      // If port detection fails, fall back to the original default
      return _defaultBaseUrl;
    }
  }

  /// Builds the WebSocket URL using dynamic port detection or falls back to default
  static String _buildDynamicWsUrl() {
    try {
      return PortDetector.buildWebSocketUrl(
        defaultHost: '[::1]',
        defaultPort: '8080',
        defaultProtocol: 'ws',
      );
    } catch (e) {
      // If port detection fails, fall back to the original default
      return _defaultWsUrl;
    }
  }

  // HTTP API Methods
  Future<DashboardStats> getStats() async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/stats'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as Map<String, dynamic>;
      return DashboardStats.fromJson(json);
    } else {
      throw ApiException('Failed to get stats: ${response.statusCode}');
    }
  }

  Future<List<StatsHistory>> getStatsHistory({int days = 7}) async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/stats/history?days=$days'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => StatsHistory.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get stats history: ${response.statusCode}');
    }
  }

  Future<List<Achievement>> getAchievements({int limit = 5}) async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/achievements?limit=$limit'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => Achievement.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get achievements: ${response.statusCode}');
    }
  }

  Future<List<Activity>> getActivities({int limit = 100}) async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/activities?limit=$limit'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => Activity.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get activities: ${response.statusCode}');
    }
  }

  Future<List<FocusSession>> getFocusSessions({int limit = 50}) async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/focus-sessions?limit=$limit'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => FocusSession.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get focus sessions: ${response.statusCode}');
    }
  }

  Future<Map<String, int>> getXPBreakdown({String? date}) async {
    final url = date != null ? '$_baseUrl/api/xp-breakdown?date=$date' : '$_baseUrl/api/xp-breakdown';

    final response = await _httpClient.get(Uri.parse(url), headers: {'Content-Type': 'application/json'});

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as Map<String, dynamic>;
      return json.map((key, value) => MapEntry(key, value as int));
    } else {
      throw ApiException('Failed to get XP breakdown: ${response.statusCode}');
    }
  }

  Future<SystemLogResponse> getLogs({int count = 100, LogLevel? level}) async {
    final url = level != null
        ? '$_baseUrl/api/logs?count=$count&level=${level.name}'
        : '$_baseUrl/api/logs?count=$count';

    final response = await _httpClient.get(Uri.parse(url), headers: {'Content-Type': 'application/json'});

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as Map<String, dynamic>;
      return SystemLogResponse.fromJson(json);
    } else {
      throw ApiException('Failed to get logs: ${response.statusCode}');
    }
  }

  Future<Map<String, dynamic>> getConfig() async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/config'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      return jsonDecode(response.body) as Map<String, dynamic>;
    } else {
      throw ApiException('Failed to get config: ${response.statusCode}');
    }
  }

  Future<void> updateConfig(Map<String, dynamic> updates) async {
    final response = await _httpClient.post(
      Uri.parse('$_baseUrl/api/config'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(updates),
    );

    if (response.statusCode != 200) {
      throw ApiException('Failed to update config: ${response.statusCode}');
    }
  }

  Future<List<ApplicationClassification>> getClassifications() async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/classifications'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => ApplicationClassification.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get classifications: ${response.statusCode}');
    }
  }

  Future<void> saveClassification(ClassificationRequest request) async {
    final response = await _httpClient.post(
      Uri.parse('$_baseUrl/api/classifications'),
      headers: {'Content-Type': 'application/json'},
      body: jsonEncode(request.toJson()),
    );

    if (response.statusCode != 200) {
      throw ApiException('Failed to save classification: ${response.statusCode}');
    }
  }

  Future<void> deleteClassification(String applicationName) async {
    final encodedName = Uri.encodeComponent(applicationName);
    final response = await _httpClient.delete(
      Uri.parse('$_baseUrl/api/classifications/$encodedName'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode != 200) {
      throw ApiException('Failed to delete classification: ${response.statusCode}');
    }
  }

  Future<List<UnclassifiedApplication>> getUnclassified() async {
    final response = await _httpClient.get(
      Uri.parse('$_baseUrl/api/unclassified'),
      headers: {'Content-Type': 'application/json'},
    );

    if (response.statusCode == 200) {
      final json = jsonDecode(response.body) as List<dynamic>;
      return json.map((item) => UnclassifiedApplication.fromJson(item as Map<String, dynamic>)).toList();
    } else {
      throw ApiException('Failed to get unclassified applications: ${response.statusCode}');
    }
  }

  // WebSocket Methods
  Future<WebSocketChannel> connectWebSocket() async {
    await _wsChannel?.sink.close();
    _wsChannel = WebSocketChannel.connect(Uri.parse(_wsUrl));
    return _wsChannel!;
  }

  Future<void> disconnectWebSocket() async {
    await _wsChannel?.sink.close();
    _wsChannel = null;
  }

  Stream<WebSocketMessage> get webSocketStream {
    if (_wsChannel == null) {
      throw StateError('WebSocket not connected. Call connectWebSocket() first.');
    }

    return _wsChannel!.stream.map((data) {
      final json = jsonDecode(data as String) as Map<String, dynamic>;
      return WebSocketMessage.fromJson(json);
    });
  }

  void sendWebSocketMessage(WebSocketMessage message) {
    if (_wsChannel == null) {
      throw StateError('WebSocket not connected. Call connectWebSocket() first.');
    }

    _wsChannel!.sink.add(jsonEncode(message.toJson()));
  }

  Future<void> dispose() async {
    _httpClient.close();
    await disconnectWebSocket();
  }
}

class ApiException implements Exception {
  final String message;

  ApiException(this.message);

  @override
  String toString() => 'ApiException: $message';
}
