// Legacy iop Edge registration client.
//
// This file contains the proto-socket based EdgeRegistrationClient used by the
// legacy iop Edge compatibility path.  Normal production runner code should
// import registration_client.dart instead.
//
// The legacy iop smoke test (oto_iop_connection_smoke_test.dart) imports this
// file explicitly so that it remains available as migration evidence without
// pulling legacy iop proto/proto-socket code into the default runner path.

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;
import 'package:oto/oto/agent/registration_client.dart';

export 'package:oto/oto/agent/registration_client.dart'
    show AgentSession, RegistrationResult, RegistrationClient;

// Legacy alias for code that still uses the old name.
typedef EdgeAgentSession = AgentSession;

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);
  }
}

RegistrationResult _registrationResultFromIopResponse(
  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 _OtoEdgeAgentSession implements AgentSession {
  final _OtoIopClient _client;

  @override
  final RegistrationResult result;

  _OtoEdgeAgentSession(this._client, this.result);

  @override
  Future<void> close() => _client.close();
}

class EdgeRegistrationClient extends RegistrationClient {
  /// Edge에 연결해 register 요청을 보낸 뒤, 연결을 유지한 채 session을 돌려준다.
  ///
  /// 반환된 [AgentSession] 소유자는 종료 시 반드시 [AgentSession.close]를
  /// 호출해야 한다. 연결 단계에서 실패하면 내부에서 transport를 닫고 예외를
  /// 다시 던지므로 leak이 생기지 않는다.
  @override
  Future<AgentSession> openSession(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 _OtoEdgeAgentSession(
        client,
        _registrationResultFromIopResponse(response),
      );
    } catch (_) {
      await client.close();
      rethrow;
    }
  }

  /// 연결을 유지하지 않는 one-shot 등록. 결과를 받은 뒤 즉시 연결을 닫는다.
  @override
  Future<RegistrationResult> register(AgentConfig config) async {
    final session = await openSession(config);
    try {
      return session.result;
    } finally {
      await session.close();
    }
  }
}
