import 'dart:async';
import 'dart:io';

import 'package:flutter/services.dart';
import 'package:mime/mime.dart';
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as shelf_io;

class CastServer {
  HttpServer? _server;
  String? _ip;

  bool get isRunning => _server != null;

  // ------------------------------------------------------------
  // TIMING DEBUG
  // ------------------------------------------------------------
  static final Stopwatch _timingSw = Stopwatch()..start();

  static int _ms() => _timingSw.elapsedMilliseconds;

  static String _isoNow() => DateTime.now().toIso8601String();

  static void _tlog(String message) {
    print('CASTT [${_ms()} ms][${_isoNow()}] $message');
  }

  // ============================================================
  // START SERVER — Versione identica al vecchio AVES + timing logs
  // ============================================================
  Future<void> start() async {
    if (_server != null) {
      _tlog('castServer.start skip already-running ip=$_ip port=${_server?.port}');
      return;
    }

    final sw = Stopwatch()..start();
    _tlog('castServer.start begin');

    // 1) Ottieni IP Wi-Fi reale
    final interfaces = await NetworkInterface.list(
      includeLoopback: false,
      type: InternetAddressType.IPv4,
    );

    _tlog('castServer.start interfaces found count=${interfaces.length} ms=${sw.elapsedMilliseconds}');

    final wifi = interfaces.firstWhere(
      (i) => i.name == 'wlan0' || i.name.contains('wifi'),
      orElse: () => interfaces.first,
    );

    _ip = wifi.addresses.first.address;
    _tlog('castServer.start selected interface=${wifi.name} ip=$_ip ms=${sw.elapsedMilliseconds}');

    // 2) Bind su 0.0.0.0 e porta random (0)
    _server = await HttpServer.bind(
      InternetAddress.anyIPv4,
      0,
      shared: true,
    );

    final port = _server!.port;
    _tlog('castServer.start bound ip=$_ip port=$port ms=${sw.elapsedMilliseconds}');

    // 3) Avvia Shelf sopra HttpServer esistente
    final handler = const Pipeline().addHandler(_handleRequest);
    shelf_io.serveRequests(_server!, handler);

    _tlog('castServer.start serveRequests active url=http://$_ip:$port ms=${sw.elapsedMilliseconds}');
  }

  // ============================================================
  // URL BUILDER — usa ID reale del MediaStore
  // ============================================================
  String buildUrlForEntry(dynamic entry) {
    final uri = entry.uri;
    final mediaId = uri.split('/').last;

    final port = _server?.port ?? 0;
    final url = 'http://$_ip:$port/media/$mediaId';
    _tlog('castServer.buildUrlForEntry mediaId=$mediaId url=$url');
    return url;
  }

  // ============================================================
  // HANDLER HTTP
  // ============================================================
  static Future<Response> _handleRequest(Request request) async {
    final sw = Stopwatch()..start();
    final path = request.requestedUri.path;
    final range = request.headers['range'];

    _tlog('CAST SERVER request start method=${request.method} path=$path range=$range');

    // URL tipo: /media/1000113738
    if (request.url.pathSegments.isEmpty ||
        request.url.pathSegments.first != 'media') {
      _tlog('CAST SERVER invalid path path=$path ms=${sw.elapsedMilliseconds}');
      return Response.notFound('Invalid path');
    }

    final idStr = request.url.pathSegments.length > 1
        ? request.url.pathSegments[1]
        : null;

    final id = int.tryParse(idStr ?? '');
    if (id == null) {
      _tlog('CAST SERVER invalid id raw=$idStr ms=${sw.elapsedMilliseconds}');
      return Response.notFound('Invalid ID');
    }

    final lookupSw = Stopwatch()..start();
    final filePath = await _getFilePath(id);
    _tlog('CAST SERVER getFilePath done id=$id ms=${lookupSw.elapsedMilliseconds} path=$filePath');

    if (filePath == null) {
      _tlog('CAST SERVER No file for ID=$id totalMs=${sw.elapsedMilliseconds}');
      return Response.notFound('No file for ID=$id');
    }

    final file = File(filePath);
    final exists = await file.exists();
    if (!exists) {
      _tlog('CAST SERVER file missing id=$id path=$filePath totalMs=${sw.elapsedMilliseconds}');
      return Response.notFound('File not found');
    }

    final fileLength = await file.length();
    final mime = lookupMimeType(file.path) ?? 'application/octet-stream';
    _tlog('CAST SERVER file ready id=$id size=$fileLength mime=$mime totalMs=${sw.elapsedMilliseconds}');

    return _serveFileWithRange(
      file,
      request,
      id: id,
      requestStopwatch: sw,
      fileLength: fileLength,
      mime: mime,
    );
  }

  // ============================================================
  // RANGE / 206 (necessario per Chromecast)
  // ============================================================
  static Future<Response> _serveFileWithRange(
    File file,
    Request request, {
    required int id,
    required Stopwatch requestStopwatch,
    required int fileLength,
    required String mime,
  }) async {
    final range = request.headers['range'];

    _tlog('CAST SERVER response preparing id=$id method=${request.method} range=$range totalMs=${requestStopwatch.elapsedMilliseconds}');

    // HEAD
    if (request.method == 'HEAD') {
      _tlog('CAST SERVER HEAD response id=$id size=$fileLength totalMs=${requestStopwatch.elapsedMilliseconds}');
      return Response.ok(
        null,
        headers: {
          'Content-Type': mime,
          'Content-Length': '$fileLength',
          'Accept-Ranges': 'bytes',
        },
      );
    }

    // Nessun range → 200
    if (range == null || !range.startsWith('bytes=')) {
      final stream = _logStreamCompletion(
        file.openRead(),
        label: 'CAST SERVER 200 stream',
        id: id,
        startOffset: 0,
        endOffset: fileLength - 1,
        declaredLength: fileLength,
        requestStopwatch: requestStopwatch,
      );

      _tlog('CAST SERVER 200 response start id=$id bytes=0-${fileLength - 1}/$fileLength totalMs=${requestStopwatch.elapsedMilliseconds}');
      return Response.ok(
        stream,
        headers: {
          'Content-Type': mime,
          'Content-Length': '$fileLength',
          'Accept-Ranges': 'bytes',
        },
      );
    }

    // Range parsing
    final spec = range.substring('bytes='.length).trim();
    final parts = spec.split('-');
    int? start = int.tryParse(parts.first);
    int? end = parts.length > 1 ? int.tryParse(parts[1]) : null;

    if (start == null && end != null) {
      final suffix = end;
      start = (fileLength - suffix).clamp(0, fileLength);
      end = fileLength - 1;
    }

    start ??= 0;
    end ??= fileLength - 1;

    if (start < 0) start = 0;
    if (end >= fileLength) end = fileLength - 1;

    final contentLength = end - start + 1;
    final rawStream = file.openRead(start, end + 1);
    final stream = _logStreamCompletion(
      rawStream,
      label: 'CAST SERVER 206 stream',
      id: id,
      startOffset: start,
      endOffset: end,
      declaredLength: contentLength,
      requestStopwatch: requestStopwatch,
    );

    _tlog('CAST SERVER 206 response start id=$id range=$start-$end/$fileLength contentLength=$contentLength totalMs=${requestStopwatch.elapsedMilliseconds}');

    return Response(
      206,
      body: stream,
      headers: {
        'Content-Type': mime,
        'Content-Length': '$contentLength',
        'Content-Range': 'bytes $start-$end/$fileLength',
        'Accept-Ranges': 'bytes',
      },
    );
  }

  static Stream<List<int>> _logStreamCompletion(
    Stream<List<int>> source, {
    required String label,
    required int id,
    required int startOffset,
    required int endOffset,
    required int declaredLength,
    required Stopwatch requestStopwatch,
  }) async* {
    int sentBytes = 0;
    final streamSw = Stopwatch()..start();
    _tlog('$label begin id=$id from=$startOffset to=$endOffset declaredLength=$declaredLength totalMs=${requestStopwatch.elapsedMilliseconds}');

    try {
      await for (final chunk in source) {
        sentBytes += chunk.length;
        yield chunk;
      }
      _tlog('$label end id=$id sentBytes=$sentBytes streamMs=${streamSw.elapsedMilliseconds} totalMs=${requestStopwatch.elapsedMilliseconds}');
    } catch (e) {
      _tlog('$label error id=$id sentBytes=$sentBytes streamMs=${streamSw.elapsedMilliseconds} totalMs=${requestStopwatch.elapsedMilliseconds} error=$e');
      rethrow;
    }
  }

  // ============================================================
  // Recupero path dal DB AVES
  // ============================================================
  static Future<String?> _getFilePath(int id) async {
    const channel = MethodChannel('aves/entry_path');
    final sw = Stopwatch()..start();
    _tlog('CAST SERVER getFilePath start id=$id');

    try {
      final path = await channel.invokeMethod<String>(
        'getEntryPath',
        {'id': id},
      );
      _tlog('CAST SERVER getFilePath end id=$id ms=${sw.elapsedMilliseconds} path=$path');
      return path;
    } catch (e) {
      _tlog('CAST SERVER getFilePath error id=$id ms=${sw.elapsedMilliseconds} error=$e');
      return null;
    }
  }

  Future<void> ensureStarted() async {
    if (!isRunning) {
      await start();
    }
  }
}

final castServer = CastServer();
