// Copyright (c) 2025, Flutter Inspector MCP Server authors.
// Licensed under the MIT License.

// ignore_for_file: avoid_catches_without_on_clauses

import 'dart:async';

import 'package:dart_mcp/server.dart';
import 'package:dtd/dtd.dart';
import 'package:flutter_mcp_toolkit_server/flutter_mcp_server.dart';
import 'package:flutter_mcp_toolkit_server/src/capabilities/dynamic_registry/dynamic_registry.dart';
import 'package:flutter_mcp_toolkit_server/src/mcp_toolkit_consts.dart';
import 'package:from_json_to_json/from_json_to_json.dart';
import 'package:rx/constructors.dart';
import 'package:rx/converters.dart';
import 'package:vm_service/vm_service.dart';

/// Registry discovery service that leverages DTD events and
/// direct VM connection
/// Uses the insight that when VM service connects, we're already
/// connected to the Flutter isolate
final class RegistryDiscoveryService {
  RegistryDiscoveryService({
    required this.dynamicRegistry,
    required this.server,
  });

  final DynamicRegistry dynamicRegistry;
  LoggingSupport get logger => server;
  final MCPToolkitServer server;
  VmService? get vmService => server.vmService;
  DartToolingDaemon? get dtd => server.dartToolingDaemon;
  static const _loggerName = 'RegistryDiscovery';
  StreamSubscription<DTDEvent>? _discoverySubscription;

  Future<void> dispose() async {
    try {
      await _discoverySubscription?.cancel();
    } catch (e, stackTrace) {
      logger.log(
        LoggingLevel.warning,
        'Error disposing registry discovery: $e'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      );
    }
  }

  /// Start simplified discovery - immediately register and listen for changes
  Future<void> startDiscovery() async {
    logger.log(
      LoggingLevel.info,
      'Starting registry discovery',
      logger: _loggerName,
    );

    // Listen for DTD events for re-registration
    _discoverySubscription = _listenForToolChanges();
  }

  /// Listen for DTD events that indicate tool changes
  StreamSubscription<DTDEvent>? _listenForToolChanges() {
    final dtd = this.dtd;
    if (dtd == null) {
      logger.log(
        LoggingLevel.warning,
        'DTD not available for event listening',
        logger: _loggerName,
      );
      return null;
    }

    logger.log(
      LoggingLevel.info,
      'Setting up DTD event listener for tool changes',
      logger: _loggerName,
    );

    final mergedStream = merge<DTDEvent>([
      dtd.onEvent(EventStreams.kExtension).toObservable(),
      dtd.onEvent(EventStreams.kService).toObservable(),
    ]);

    final listener = mergedStream.toStream().listen(
      (final e) {
        // final method = e.data['method'];
        if (e.kind == EventKind.kServiceRegistered) {
          logger.log(
            LoggingLevel.info,
            'Service registered: $e',
            logger: _loggerName,
          );
          unawaited(registerToolsAndResources());
        }
        unawaited(_handleMCPToolkitEvent(e));
      },
      onError: (final error, final stackTrace) => logger.log(
        LoggingLevel.warning,
        'Error in DTD event listener: $error'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      ),
    );

    try {
      // Listen to the MCPToolkit stream for tool registration events
      return listener;
    } catch (e, stackTrace) {
      logger.log(
        LoggingLevel.warning,
        'Failed to set up DTD event listener: $e'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      );
      return null;
    }
  }

  /// Handle MCP Toolkit events from DTD
  Future<void> _handleMCPToolkitEvent(final DTDEvent event) async {
    try {
      final eventData = event.data;
      final eventKind = jsonDecodeString(eventData['kind']);

      logger.log(
        LoggingLevel.debug,
        'Received MCP Toolkit event: $eventKind',
        logger: _loggerName,
      );

      switch (eventKind) {
        case 'ToolRegistration':
          // Flutter app has registered new tools - re-register everything
          await registerToolsAndResources();
        case 'ServiceExtensionStateChanged':
          // Tool state changed - might need re-registration
          final extensionName = jsonDecodeString(eventData['extension']);
          if (extensionName.contains(mcpToolkitExtNames.registerDynamics)) {
            await registerToolsAndResources();
          }
        default:
          logger.log(
            LoggingLevel.debug,
            'Ignoring MCP Toolkit event: $eventKind',
            logger: _loggerName,
          );
      }
    } catch (e, stackTrace) {
      logger.log(
        LoggingLevel.warning,
        'Error handling MCP Toolkit event: $e'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      );
    }
  }

  /// Register tools from the Flutter isolate
  Future<void> registerToolsAndResources() async {
    try {
      logger.log(
        LoggingLevel.info,
        'Calling registerDynamic',
        logger: _loggerName,
      );

      final response = await server.callFlutterExtension(
        '$mcpToolkitExt.${mcpToolkitExtNames.registerDynamics}',
      );

      final data = jsonDecodeMap(response.json);
      await _processRegistrationResponse(data);
    } catch (e, stackTrace) {
      logger.log(
        LoggingLevel.error,
        'Failed to call registerDynamics: $e'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      );
    }
  }

  /// Process the response from registerDynamics
  Future<void> _processRegistrationResponse(
    final Map<String, dynamic> data,
  ) async {
    try {
      final appId = DynamicAppId(jsonDecodeString(data['appId']));
      final tools = jsonDecodeListAs<Map<String, dynamic>>(data['tools']);
      final resources = jsonDecodeListAs<Map<String, dynamic>>(
        data['resources'],
      );

      logger.log(
        LoggingLevel.info,
        'Processing registration: ${tools.length} tools, '
        '${resources.length} resources from $appId',
        logger: _loggerName,
      );

      // Clear existing registrations for this app
      server.unregisterDynamicApp(appId);

      // Register tools
      for (final toolData in tools) {
        try {
          final tool = Tool.fromMap(toolData);
          if (allMcpToolkitExtNames.contains(tool.name)) {
            continue;
          }
          server.registerDynamicTool(tool, appId);
        } catch (e) {
          logger.log(
            LoggingLevel.warning,
            'Failed to register tool ${toolData['name']}: $e',
            logger: _loggerName,
          );
        }
      }

      // Register resources
      for (final resourceData in resources) {
        try {
          final resource = Resource.fromMap(resourceData);
          if (allMcpToolkitExtNames.contains(resource.uri)) {
            continue;
          }
          server.registerDynamicResource(resource, appId);
        } catch (e) {
          logger.log(
            LoggingLevel.warning,
            'Failed to register resource ${resourceData['uri']}: $e',
            logger: _loggerName,
          );
        }
      }

      logger.log(
        LoggingLevel.info,
        'Successfully registered $appId with ${tools.length} '
        'tools and ${resources.length} resources',
        logger: _loggerName,
      );
    } catch (e, stackTrace) {
      logger.log(
        LoggingLevel.error,
        'Failed to process registration response: $e'
        'stackTrace: $stackTrace',
        logger: _loggerName,
      );
    }
  }
}
