import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_static/shelf_static.dart';
import 'package:shelf_web_socket/shelf_web_socket.dart';
import 'package:sqlite3/sqlite3.dart';
import '../database/database_manager.dart';
import '../config/config_manager.dart';
import '../logging/logger.dart';
import 'websocket_manager.dart';

class DashboardServer {
  static DashboardServer? _instance;
  static DashboardServer get instance => _instance ??= DashboardServer._();

  DashboardServer._() : _dbManager = DatabaseManager(sqlite3.open('productivity_tracker.db'));

  HttpServer? _server;
  final DatabaseManager _dbManager;
  final ConfigManager _configManager = ConfigManager.instance;
  int _port = 8080;

  DashboardServer.withDatabase(this._dbManager);

  Future<void> start([int port = 8080]) async {
    _port = port;

    final router = Router();

    // API Routes
    router.get('/api/stats', _handleStats);
    router.get('/api/stats/history', _handleStatsHistory);
    router.get('/api/achievements', _handleAchievements);
    router.get('/api/activities', _handleActivities);
    router.get('/api/focus-sessions', _handleFocusSessions);
    router.get('/api/xp-breakdown', _handleXPBreakdown);
    router.get('/api/logs', _handleLogs);
    router.get('/api/config', _handleGetConfig);
    router.post('/api/config', _handleUpdateConfig);
    router.get('/api/classifications', _handleGetClassifications);
    router.post('/api/classifications', _handleSaveClassification);
    router.delete('/api/classifications/<application>', _handleDeleteClassification);
    router.get('/api/unclassified', _handleGetUnclassified);

    // WebSocket for real-time updates
    router.get('/ws', _handleWebSocket());

    // Static file handler for the web UI
    final staticHandler = createStaticHandler('lib/src/web/static', defaultDocument: 'index.html');

    final cascade = Cascade().add(router).add(staticHandler);

    final handler = Pipeline()
        .addMiddleware(logRequests())
        .addMiddleware(_corsMiddleware())
        .addHandler(cascade.handler);

    try {
      _server = await shelf_io.serve(handler, InternetAddress.anyIPv6, _port);
      Logger.info('Dashboard server started on http://[::]:$_port (IPv4 and IPv6)');
    } catch (e) {
      Logger.error('Failed to start dashboard server: $e');
      rethrow;
    }
  }

  Future<void> stop() async {
    await _server?.close();
    _server = null;
    Logger.info('Dashboard server stopped');
  }

  Middleware _corsMiddleware() {
    return (Handler innerHandler) {
      return (Request request) async {
        if (request.method == 'OPTIONS') {
          return Response.ok('', headers: _corsHeaders);
        }

        final response = await innerHandler(request);
        return response.change(headers: _corsHeaders);
      };
    };
  }

  Map<String, String> get _corsHeaders => {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
  };

  Future<Response> _handleStats(Request request) async {
    try {
      final stats = _dbManager.getTodayStats();
      final streaks = _dbManager.getStreakStats();
      final recentActivity = _dbManager.getRecentActivity(5);

      final response = {
        'today': stats,
        'streaks': streaks,
        'recent_activity': recentActivity
            .map(
              (row) => {
                'type': row['type'],
                'application': row['application'],
                'timestamp': _convertTimestamp(row['timestamp']),
                'duration_seconds': row['duration_seconds'],
              },
            )
            .toList(),
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };

      return Response.ok(jsonEncode(response), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling stats request: $e');
      return Response.internalServerError(body: 'Failed to get stats');
    }
  }

  Future<Response> _handleStatsHistory(Request request) async {
    try {
      final days = int.tryParse(request.url.queryParameters['days'] ?? '7') ?? 7;
      final history = _getStatsHistory(days);

      return Response.ok(jsonEncode(history), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling stats history request: $e');
      return Response.internalServerError(body: 'Failed to get stats history');
    }
  }

  List<Map<String, dynamic>> _getStatsHistory(int days) {
    final history = <Map<String, dynamic>>[];
    final now = DateTime.now();

    for (int i = days - 1; i >= 0; i--) {
      final date = now.subtract(Duration(days: i));
      final dateStr = date.toIso8601String().substring(0, 10);

      final stats = _dbManager.getDailyStatsForDate(dateStr);

      if (stats.isNotEmpty) {
        final row = stats.first;
        history.add({
          'date': dateStr,
          'level': row['level'],
          'xp': row['total_xp'],
          'focus_time': row['focus_time_seconds'],
          'meeting_time': row['meeting_time_seconds'],
        });
      } else {
        history.add({'date': dateStr, 'level': 1, 'xp': 0, 'focus_time': 0, 'meeting_time': 0});
      }
    }

    return history;
  }

  Future<Response> _handleAchievements(Request request) async {
    try {
      final achievements = _dbManager.getAllAchievements();

      final achievementList = achievements
          .map(
            (row) => {
              'id': row['id'],
              'name': row['name'],
              'description': row['description'],
              'xp_reward': row['xp_reward'],
              'achieved_at': row['achieved_at'] != null ? _convertTimestamp(row['achieved_at']) : null,
              'level_at_achievement': row['level_at_achievement'],
            },
          )
          .toList();

      return Response.ok(jsonEncode(achievementList), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling achievements request: $e');
      return Response.internalServerError(body: 'Failed to get achievements');
    }
  }

  Future<Response> _handleActivities(Request request) async {
    try {
      final limit = int.tryParse(request.url.queryParameters['limit'] ?? '100') ?? 100;
      final activities = _dbManager.getRecentActivities(limit);

      final activityList = activities
          .map(
            (row) => {
              'id': row['id'],
              'type': row['type'],
              'application': row['application'],
              'metadata': row['metadata'] != null ? jsonDecode(row['metadata']) : null,
              'timestamp': _convertTimestamp(row['timestamp']),
              'duration_seconds': row['duration_seconds'],
            },
          )
          .toList();

      return Response.ok(jsonEncode(activityList), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling activities request: $e');
      return Response.internalServerError(body: 'Failed to get activities');
    }
  }

  Future<Response> _handleFocusSessions(Request request) async {
    try {
      final limit = int.tryParse(request.url.queryParameters['limit'] ?? '50') ?? 50;
      final sessions = _dbManager.getRecentFocusSessions(limit);

      final sessionList = sessions
          .map(
            (row) => {
              'id': row['id'],
              'date': row['date'],
              'duration_minutes': row['duration_minutes'],
              'bonus_xp': row['bonus_xp'],
              'timestamp': row['timestamp'],
            },
          )
          .toList();

      return Response.ok(jsonEncode(sessionList), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling focus sessions request: $e');
      return Response.internalServerError(body: 'Failed to get focus sessions');
    }
  }

  Future<Response> _handleXPBreakdown(Request request) async {
    try {
      final date = request.url.queryParameters['date'];
      final Map<String, int> breakdown;

      if (date != null) {
        breakdown = _dbManager.getXPBreakdownForDate(date);
      } else {
        breakdown = _dbManager.getTodayXPBreakdown();
      }

      return Response.ok(jsonEncode(breakdown), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling XP breakdown request: $e');
      return Response.internalServerError(body: 'Failed to get XP breakdown');
    }
  }

  Future<Response> _handleLogs(Request request) async {
    try {
      final count = int.tryParse(request.url.queryParameters['count'] ?? '100') ?? 100;
      final level = request.url.queryParameters['level'];

      List<String> logs;
      if (level != null) {
        final logLevel = LogLevel.values.firstWhere(
          (l) => l.name.toLowerCase() == level.toLowerCase(),
          orElse: () => LogLevel.info,
        );
        logs = await Logger.instance.getLogsByLevel(logLevel, count);
      } else {
        logs = await Logger.instance.getRecentLogs(count);
      }

      return Response.ok(jsonEncode({'logs': logs}), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling logs request: $e');
      return Response.internalServerError(body: 'Failed to get logs');
    }
  }

  Future<Response> _handleGetConfig(Request request) async {
    try {
      final config = _configManager.getAllConfig();
      return Response.ok(jsonEncode(config), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling get config request: $e');
      return Response.internalServerError(body: 'Failed to get config');
    }
  }

  Future<Response> _handleUpdateConfig(Request request) async {
    try {
      final body = await request.readAsString();
      final updates = jsonDecode(body) as Map<String, dynamic>;

      for (final entry in updates.entries) {
        await _configManager.updateConfig(entry.key, entry.value);
      }

      return Response.ok(
        jsonEncode({'success': true, 'message': 'Configuration updated'}),
        headers: {'Content-Type': 'application/json'},
      );
    } catch (e) {
      Logger.error('Error handling update config request: $e');
      return Response.internalServerError(body: 'Failed to update config');
    }
  }

  Future<Response> _handleGetClassifications(Request request) async {
    try {
      final classifications = _dbManager.getAllApplicationClassifications();

      final classificationList = classifications
          .map(
            (row) => {
              'id': row['id'],
              'application_name': row['application_name'],
              'category_id': row['category_id'],
              'created_at': _convertTimestamp(row['created_at']),
              'updated_at': _convertTimestamp(row['updated_at']),
            },
          )
          .toList();

      return Response.ok(jsonEncode(classificationList), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling get classifications request: $e');
      return Response.internalServerError(body: 'Failed to get classifications');
    }
  }

  Future<Response> _handleSaveClassification(Request request) async {
    try {
      final body = await request.readAsString();
      final data = jsonDecode(body) as Map<String, dynamic>;

      final applicationName = data['application_name'] as String?;
      final categoryId = data['category_id'] as String?;

      if (applicationName == null || categoryId == null) {
        return Response.badRequest(body: 'Missing application_name or category_id');
      }

      _dbManager.saveApplicationClassification(applicationName, categoryId);

      return Response.ok(
        jsonEncode({'success': true, 'message': 'Classification saved'}),
        headers: {'Content-Type': 'application/json'},
      );
    } catch (e) {
      Logger.error('Error handling save classification request: $e');
      return Response.internalServerError(body: 'Failed to save classification');
    }
  }

  Future<Response> _handleDeleteClassification(Request request) async {
    try {
      final applicationName = request.params['application'];
      if (applicationName == null) {
        return Response.badRequest(body: 'Missing application name');
      }

      // URL decode the application name
      final decodedName = Uri.decodeComponent(applicationName);
      _dbManager.deleteApplicationClassification(decodedName);

      return Response.ok(
        jsonEncode({'success': true, 'message': 'Classification deleted'}),
        headers: {'Content-Type': 'application/json'},
      );
    } catch (e) {
      Logger.error('Error handling delete classification request: $e');
      return Response.internalServerError(body: 'Failed to delete classification');
    }
  }

  Future<Response> _handleGetUnclassified(Request request) async {
    try {
      final unclassified = _dbManager.getUnclassifiedApplications();

      final unclassifiedList = unclassified
          .map(
            (row) => {
              'id': row['id'],
              'application_name': row['application_name'],
              'first_seen': _convertTimestamp(row['first_seen']),
              'last_seen': _convertTimestamp(row['last_seen']),
              'occurrence_count': row['occurrence_count'],
            },
          )
          .toList();

      return Response.ok(jsonEncode(unclassifiedList), headers: {'Content-Type': 'application/json'});
    } catch (e) {
      Logger.error('Error handling get unclassified request: $e');
      return Response.internalServerError(body: 'Failed to get unclassified applications');
    }
  }

  Handler _handleWebSocket() {
    return webSocketHandler((webSocket) {
      Logger.info('New WebSocket connection established');
      WebSocketManager.instance.addConnection(webSocket);
    });
  }

  String get dashboardUrl => 'http://localhost:$_port';

  /// Converts a database timestamp (int) to milliseconds since epoch for JSON serialization
  int _convertTimestamp(dynamic timestamp) {
    if (timestamp is int) {
      // Assume database stores seconds, convert to milliseconds
      return timestamp * 1000;
    }
    return timestamp as int;
  }
}
