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

import 'package:proto_socket/proto_socket.dart';
import 'package:oto/oto/agent/agent_config.dart';
import 'package:oto/oto/agent/iop/runtime.pb.dart' as iop;

class EdgeEndpoint {
  final String host;
  final int port;

  EdgeEndpoint({required this.host, required this.port});

  factory EdgeEndpoint.parse(String url) {
    String parsedUrl = url;
    if (!url.contains('://')) {
      parsedUrl = 'http://$url';
    }
    final uri = Uri.parse(parsedUrl);
    final host = uri.host;
    int port = uri.port;
    if (!uri.hasPort) {
      if (uri.scheme == 'https') {
        port = 443;
      } else {
        port = 80;
      }
    }
    if (host.isEmpty) {
      throw FormatException('Invalid edge url: "$url"');
    }
    return EdgeEndpoint(host: host, port: port);
  }
}

class RegistrationResult {
  final bool accepted;
  final String? rejectReason;
  final String? nodeId;
  final String? alias;
  final Map<String, dynamic>? runtimeConfig;

  RegistrationResult._({
    required this.accepted,
    this.rejectReason,
    this.nodeId,
    this.alias,
    this.runtimeConfig,
  });

  factory RegistrationResult.accepted(
    String nodeId,
    String? alias,
    Map<String, dynamic> runtimeConfig,
  ) {
    return RegistrationResult._(
      accepted: true,
      nodeId: nodeId,
      alias: alias,
      runtimeConfig: runtimeConfig,
    );
  }

  factory RegistrationResult.rejected(String reason) {
    return RegistrationResult._(
      accepted: false,
      rejectReason: reason,
    );
  }

  factory RegistrationResult.fromResponse(iop.RegisterResponse response) {
    if (!response.accepted) {
      return RegistrationResult.rejected(response.reason);
    }

    final runtimeMap = <String, dynamic>{};
    if (response.hasConfig() && response.config.hasRuntime()) {
      runtimeMap['concurrency'] = response.config.runtime.concurrency.toInt();
      runtimeMap['workspaceRoot'] = response.config.runtime.workspaceRoot;
    }

    return RegistrationResult.accepted(
      response.nodeId,
      response.alias.isEmpty ? null : response.alias,
      runtimeMap,
    );
  }
}

class _OtoIopClient extends ProtobufClient {
  _OtoIopClient(Socket socket)
      : super(socket, 30, 45, {
          iop.RegisterResponse.getDefault().info_.qualifiedMessageName:
              iop.RegisterResponse.fromBuffer,
          iop.RunRequest.getDefault().info_.qualifiedMessageName:
              iop.RunRequest.fromBuffer,
          iop.CancelRequest.getDefault().info_.qualifiedMessageName:
              iop.CancelRequest.fromBuffer,
          iop.NodeCommandRequest.getDefault().info_.qualifiedMessageName:
              iop.NodeCommandRequest.fromBuffer,
          iop.EdgeNodeEvent.getDefault().info_.qualifiedMessageName:
              iop.EdgeNodeEvent.fromBuffer,
        });
}

class EdgeRegistrationClient {
  Future<RegistrationResult> register(AgentConfig config) async {
    final endpoint = EdgeEndpoint.parse(config.edge.url);
    final socket = await ProtobufClient.connect(endpoint.host, endpoint.port);
    final client = _OtoIopClient(socket);
    try {
      final response =
          await client.sendRequest<iop.RegisterRequest, iop.RegisterResponse>(
        iop.RegisterRequest()..token = config.agent.enrollmentToken,
        timeout: const Duration(seconds: 5),
      );
      return RegistrationResult.fromResponse(response);
    } finally {
      await client.close();
    }
  }
}
