import * as grpc from '@grpc/grpc-js';
import * as protoLoader from '@grpc/proto-loader';
import * as path from 'path';
import { EventEmitter } from 'events';
import { app } from 'electron';

// 定义 proto 包结构类型
interface ProtoPackage {
  mpc?: {
    router?: {
      v1?: {
        MessageRouter?: grpc.ServiceClientConstructor;
      };
    };
  };
}

// 延迟加载的 Proto 定义
let packageDefinition: protoLoader.PackageDefinition | null = null;

// Proto 文件路径 - 在打包后需要从 app.asar.unpacked 或 resources 目录加载
function getProtoPath(): string {
  // 开发环境
  if (!app.isPackaged) {
    return path.join(__dirname, '../../proto/message_router.proto');
  }
  // 生产环境 - proto 文件需要解包
  return path.join(process.resourcesPath, 'proto/message_router.proto');
}

// 延迟加载 Proto 定义
function loadProtoDefinition(): protoLoader.PackageDefinition {
  if (!packageDefinition) {
    const protoPath = getProtoPath();
    console.log('Loading proto from:', protoPath);
    packageDefinition = protoLoader.loadSync(protoPath, {
      keepCase: true,
      longs: String,
      enums: String,
      defaults: true,
      oneofs: true,
    });
  }
  return packageDefinition;
}

// Note: field names must match proto definitions with keepCase: true
// Proto uses snake_case: session_id, session_type, threshold_n, threshold_t
interface SessionInfo {
  session_id: string;
  session_type: string;
  threshold_n: number;
  threshold_t: number;
  message_hash?: Buffer;
  keygen_session_id?: string;
  status?: string;
}

interface PartyInfo {
  party_id: string;
  party_index: number;
}

interface JoinSessionResponse {
  success: boolean;
  session_info?: SessionInfo;
  party_index: number;
  other_parties: PartyInfo[];
}

interface MPCMessage {
  message_id: string;
  session_id: string;
  from_party: string;
  is_broadcast: boolean;
  round_number: number;
  payload: Buffer;
  created_at: string;
}

interface SessionEvent {
  event_id: string;
  event_type: string;
  session_id: string;
  threshold_n: number;
  threshold_t: number;
  selected_parties: string[];
  join_tokens: Record<string, string>;
  message_hash?: Buffer;
}

// Raw proto response (snake_case)
interface RegisteredPartyProto {
  party_id: string;
  role: string;
  online: boolean;
  registered_at: string;
  last_seen_at: string;
}

interface GetRegisteredPartiesResponse {
  parties: RegisteredPartyProto[];
}

// Converted response (camelCase) - used by callers
interface RegisteredParty {
  partyId: string;
  role: string;
  online: boolean;
  registeredAt: string;
  lastSeenAt: string;
}

// 重连配置
interface ReconnectConfig {
  maxRetries: number;
  initialDelayMs: number;
  maxDelayMs: number;
  backoffMultiplier: number;
}

const DEFAULT_RECONNECT_CONFIG: ReconnectConfig = {
  maxRetries: 10,
  initialDelayMs: 1000,
  maxDelayMs: 30000,
  backoffMultiplier: 2,
};

/**
 * gRPC 客户端 - 连接到 Message Router
 *
 * 连接地址格式:
 *   - 开发环境: localhost:50051 (不加密)
 *   - 生产环境: mpc-grpc.szaiai.com:443 (TLS 加密)
 *
 * 特性:
 *   - 自动重连机制（指数退避）
 *   - 事件流断开后自动重新订阅
 *   - 心跳失败后自动重连
 */
export class GrpcClient extends EventEmitter {
  private client: grpc.Client | null = null;
  private connected = false;
  private partyId: string | null = null;
  private partyRole: string | null = null;
  private heartbeatInterval: NodeJS.Timeout | null = null;
  private messageStream: grpc.ClientReadableStream<MPCMessage> | null = null;
  private eventStream: grpc.ClientReadableStream<SessionEvent> | null = null;

  // 重连相关
  private reconnectConfig: ReconnectConfig;
  private currentAddress: string | null = null;
  private currentUseTLS: boolean | undefined;
  private isReconnecting = false;
  private reconnectAttempts = 0;
  private reconnectTimeout: NodeJS.Timeout | null = null;
  private shouldReconnect = true;

  // 消息流状态（用于重连后恢复）
  private activeMessageSubscription: { sessionId: string; partyId: string } | null = null;
  private eventStreamSubscribed = false;

  // 心跳失败计数
  private heartbeatFailCount = 0;
  private readonly MAX_HEARTBEAT_FAILS = 3;

  constructor(reconnectConfig?: Partial<ReconnectConfig>) {
    super();
    this.reconnectConfig = { ...DEFAULT_RECONNECT_CONFIG, ...reconnectConfig };
  }

  /**
   * 连接到 Message Router
   * @param address 完整地址，格式: host:port (例如 mpc-grpc.szaiai.com:443 或 localhost:50051)
   * @param useTLS 是否使用 TLS 加密 (默认: 自动检测，端口 443 使用 TLS)
   */
  async connect(address: string, useTLS?: boolean): Promise<void> {
    // 保存连接参数用于重连
    this.currentAddress = address;
    this.currentUseTLS = useTLS;
    this.shouldReconnect = true;

    return this.doConnect(address, useTLS);
  }

  private async doConnect(address: string, useTLS?: boolean): Promise<void> {
    return new Promise((resolve, reject) => {
      const definition = loadProtoDefinition();
      const proto = grpc.loadPackageDefinition(definition) as ProtoPackage;
      const MessageRouter = proto.mpc?.router?.v1?.MessageRouter;

      if (!MessageRouter) {
        reject(new Error('Failed to load MessageRouter service definition'));
        return;
      }

      // 解析地址，如果没有端口则默认使用 443
      let targetAddress = address;
      if (!address.includes(':')) {
        targetAddress = `${address}:443`;
      }

      // 自动检测是否使用 TLS: 端口 443 或显式指定
      const port = parseInt(targetAddress.split(':')[1], 10);
      const shouldUseTLS = useTLS !== undefined ? useTLS : (port === 443);

      // 创建凭证
      const credentials = shouldUseTLS
        ? grpc.credentials.createSsl()  // TLS 加密 (生产环境)
        : grpc.credentials.createInsecure();  // 不加密 (开发环境)

      console.log(`[gRPC] Connecting to Message Router: ${targetAddress} (TLS: ${shouldUseTLS})`);

      this.client = new MessageRouter(
        targetAddress,
        credentials
      ) as grpc.Client;

      // 等待连接就绪
      const deadline = new Date();
      deadline.setSeconds(deadline.getSeconds() + 10);

      (this.client as grpc.Client & { waitForReady: (deadline: Date, callback: (err?: Error) => void) => void })
        .waitForReady(deadline, (err?: Error) => {
          if (err) {
            reject(err);
          } else {
            this.connected = true;
            this.reconnectAttempts = 0; // 重置重连计数
            this.heartbeatFailCount = 0;
            console.log('[gRPC] Connected successfully');
            this.emit('connected');
            resolve();
          }
        });
    });
  }

  /**
   * 断开连接（不会自动重连）
   */
  disconnect(): void {
    this.shouldReconnect = false;
    this.cleanupConnection();
  }

  /**
   * 清理连接资源
   */
  private cleanupConnection(): void {
    if (this.reconnectTimeout) {
      clearTimeout(this.reconnectTimeout);
      this.reconnectTimeout = null;
    }

    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
      this.heartbeatInterval = null;
    }

    if (this.messageStream) {
      try {
        this.messageStream.cancel();
      } catch (e) {
        // 忽略取消错误
      }
      this.messageStream = null;
    }

    if (this.eventStream) {
      try {
        this.eventStream.cancel();
      } catch (e) {
        // 忽略取消错误
      }
      this.eventStream = null;
    }

    if (this.client) {
      try {
        (this.client as grpc.Client & { close: () => void }).close();
      } catch (e) {
        // 忽略关闭错误
      }
      this.client = null;
    }

    this.connected = false;
  }

  /**
   * 触发重连
   */
  private async triggerReconnect(reason: string): Promise<void> {
    if (!this.shouldReconnect || this.isReconnecting || !this.currentAddress) {
      return;
    }

    console.log(`[gRPC] Triggering reconnect: ${reason}`);
    this.isReconnecting = true;
    this.connected = false;
    this.emit('disconnected', reason);

    // 清理现有连接
    this.cleanupConnection();

    // 计算延迟时间（指数退避）
    const delay = Math.min(
      this.reconnectConfig.initialDelayMs * Math.pow(this.reconnectConfig.backoffMultiplier, this.reconnectAttempts),
      this.reconnectConfig.maxDelayMs
    );

    console.log(`[gRPC] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1}/${this.reconnectConfig.maxRetries})`);

    this.reconnectTimeout = setTimeout(async () => {
      this.reconnectAttempts++;

      if (this.reconnectAttempts > this.reconnectConfig.maxRetries) {
        console.error('[gRPC] Max reconnect attempts reached');
        this.isReconnecting = false;
        this.emit('reconnectFailed', 'Max retries exceeded');
        return;
      }

      try {
        await this.doConnect(this.currentAddress!, this.currentUseTLS);

        // 重新注册
        if (this.partyId && this.partyRole) {
          console.log(`[gRPC] Re-registering as party: ${this.partyId}`);
          await this.registerParty(this.partyId, this.partyRole);
        }

        // 重新订阅事件流
        if (this.eventStreamSubscribed && this.partyId) {
          console.log('[gRPC] Re-subscribing to session events');
          this.subscribeSessionEvents(this.partyId);
        }

        // 重新订阅消息流
        if (this.activeMessageSubscription) {
          console.log(`[gRPC] Re-subscribing to messages for session: ${this.activeMessageSubscription.sessionId}`);
          this.subscribeMessages(this.activeMessageSubscription.sessionId, this.activeMessageSubscription.partyId);
        }

        this.isReconnecting = false;
        this.emit('reconnected');
      } catch (err) {
        console.error(`[gRPC] Reconnect attempt ${this.reconnectAttempts} failed:`, (err as Error).message);
        this.isReconnecting = false;
        // 继续尝试重连
        this.triggerReconnect('Previous reconnect attempt failed');
      }
    }, delay);
  }

  /**
   * 检查是否已连接
   */
  isConnected(): boolean {
    return this.connected;
  }

  /**
   * 获取当前 Party ID
   */
  getPartyId(): string | null {
    return this.partyId;
  }

  /**
   * 注册为参与方
   */
  async registerParty(partyId: string, role: string): Promise<void> {
    if (!this.client) {
      throw new Error('Not connected');
    }

    return new Promise((resolve, reject) => {
      (this.client as grpc.Client & { registerParty: (req: unknown, callback: (err: Error | null, res: { success: boolean }) => void) => void })
        .registerParty(
          {
            party_id: partyId,
            party_role: role,
            version: '1.0.0',
          },
          (err: Error | null, response: { success: boolean }) => {
            if (err) {
              reject(err);
            } else if (!response.success) {
              reject(new Error('Registration failed'));
            } else {
              this.partyId = partyId;
              this.partyRole = role;
              this.startHeartbeat();
              resolve();
            }
          }
        );
    });
  }

  /**
   * 开始心跳（带重连逻辑）
   */
  private startHeartbeat(): void {
    if (this.heartbeatInterval) {
      clearInterval(this.heartbeatInterval);
    }

    this.heartbeatFailCount = 0;

    this.heartbeatInterval = setInterval(() => {
      if (this.client && this.partyId) {
        (this.client as grpc.Client & { heartbeat: (req: unknown, callback: (err: Error | null) => void) => void })
          .heartbeat(
            { party_id: this.partyId },
            (err: Error | null) => {
              if (err) {
                this.heartbeatFailCount++;
                console.error(`[gRPC] Heartbeat failed (${this.heartbeatFailCount}/${this.MAX_HEARTBEAT_FAILS}):`, err.message);
                this.emit('connectionError', err);

                // 连续失败多次后触发重连
                if (this.heartbeatFailCount >= this.MAX_HEARTBEAT_FAILS) {
                  this.triggerReconnect('Heartbeat failed');
                }
              } else {
                // 心跳成功，重置失败计数
                this.heartbeatFailCount = 0;
              }
            }
          );
      }
    }, 30000); // 每 30 秒一次
  }

  /**
   * 加入会话
   */
  async joinSession(sessionId: string, partyId: string, joinToken: string): Promise<JoinSessionResponse> {
    if (!this.client) {
      throw new Error('Not connected');
    }

    return new Promise((resolve, reject) => {
      (this.client as grpc.Client & { joinSession: (req: unknown, callback: (err: Error | null, res: JoinSessionResponse) => void) => void })
        .joinSession(
          {
            session_id: sessionId,
            party_id: partyId,
            join_token: joinToken,
          },
          (err: Error | null, response: JoinSessionResponse) => {
            if (err) {
              reject(err);
            } else {
              resolve(response);
            }
          }
        );
    });
  }

  /**
   * 订阅会话事件（带自动重连）
   */
  subscribeSessionEvents(partyId: string): void {
    if (!this.client) {
      throw new Error('Not connected');
    }

    // 标记已订阅（用于重连后恢复）
    this.eventStreamSubscribed = true;

    // 取消现有流 - 先移除事件监听器再取消，防止旧流的 end 事件触发重连
    if (this.eventStream) {
      const oldStream = this.eventStream;
      oldStream.removeAllListeners();
      try {
        oldStream.cancel();
      } catch (e) {
        // 忽略
      }
    }

    this.eventStream = (this.client as grpc.Client & { subscribeSessionEvents: (req: unknown) => grpc.ClientReadableStream<SessionEvent> })
      .subscribeSessionEvents({ party_id: partyId });

    // 保存当前流的引用，用于在事件处理器中检查是否是当前活跃的流
    const currentStream = this.eventStream;

    this.eventStream.on('data', (event: SessionEvent) => {
      this.emit('sessionEvent', event);
    });

    this.eventStream.on('error', (err: Error) => {
      console.error('[gRPC] Session event stream error:', err.message);
      this.emit('streamError', err);

      // 只有当前活跃的流才触发重连，防止旧流的事件干扰
      if (currentStream !== this.eventStream) {
        console.log('[gRPC] Ignoring error from old event stream');
        return;
      }

      // 非主动取消的错误触发重连
      if (!err.message.includes('CANCELLED') && this.shouldReconnect) {
        this.triggerReconnect('Event stream error');
      }
    });

    this.eventStream.on('end', () => {
      console.log('[gRPC] Session event stream ended');
      this.emit('streamEnd');

      // 只有当前活跃的流才触发重连，防止旧流的事件干扰
      if (currentStream !== this.eventStream) {
        console.log('[gRPC] Ignoring end from old event stream');
        return;
      }

      // 流结束也触发重连
      if (this.shouldReconnect && this.eventStreamSubscribed) {
        this.triggerReconnect('Event stream ended');
      }
    });
  }

  /**
   * 取消订阅会话事件
   */
  unsubscribeSessionEvents(): void {
    this.eventStreamSubscribed = false;
    if (this.eventStream) {
      try {
        this.eventStream.cancel();
      } catch (e) {
        // 忽略
      }
      this.eventStream = null;
    }
  }

  /**
   * 订阅 MPC 消息（带自动重连）
   */
  subscribeMessages(sessionId: string, partyId: string): void {
    if (!this.client) {
      throw new Error('Not connected');
    }

    if (!this.connected) {
      throw new Error('gRPC client not connected');
    }

    // 保存订阅状态（用于重连后恢复）
    this.activeMessageSubscription = { sessionId, partyId };

    // 取消现有流 - 先移除事件监听器再取消，防止旧流的 end 事件触发重连
    if (this.messageStream) {
      const oldStream = this.messageStream;
      oldStream.removeAllListeners();
      try {
        oldStream.cancel();
      } catch (e) {
        console.log('[gRPC] Ignored error while canceling old message stream:', (e as Error).message);
      }
      this.messageStream = null;
    }

    try {
      this.messageStream = (this.client as grpc.Client & { subscribeMessages: (req: unknown) => grpc.ClientReadableStream<MPCMessage> })
        .subscribeMessages({
          session_id: sessionId,
          party_id: partyId,
        });
    } catch (e) {
      console.error('[gRPC] Failed to create message stream:', (e as Error).message);
      this.activeMessageSubscription = null;
      throw e;
    }

    // 保存当前流的引用，用于在事件处理器中检查是否是当前活跃的流
    const currentStream = this.messageStream;

    this.messageStream.on('data', (message: MPCMessage) => {
      this.emit('mpcMessage', message);
    });

    this.messageStream.on('error', (err: Error) => {
      console.error('[gRPC] Message stream error:', err.message);
      this.emit('messageStreamError', err);

      // 只有当前活跃的流才触发重连，防止旧流的事件干扰
      if (currentStream !== this.messageStream) {
        console.log('[gRPC] Ignoring error from old message stream');
        return;
      }

      // 非主动取消的错误触发重连
      if (!err.message.includes('CANCELLED') && this.shouldReconnect && this.activeMessageSubscription) {
        this.triggerReconnect('Message stream error');
      }
    });

    this.messageStream.on('end', () => {
      console.log('[gRPC] Message stream ended');
      this.emit('messageStreamEnd');

      // 只有当前活跃的流才触发重连，防止旧流的事件干扰
      if (currentStream !== this.messageStream) {
        console.log('[gRPC] Ignoring end from old message stream');
        return;
      }

      // 流结束也触发重连
      if (this.shouldReconnect && this.activeMessageSubscription) {
        this.triggerReconnect('Message stream ended');
      }
    });
  }

  /**
   * 取消订阅 MPC 消息
   */
  unsubscribeMessages(): void {
    this.activeMessageSubscription = null;
    if (this.messageStream) {
      try {
        this.messageStream.cancel();
      } catch (e) {
        // 忽略
      }
      this.messageStream = null;
    }
  }

  /**
   * 发送 MPC 消息
   */
  async routeMessage(
    sessionId: string,
    fromParty: string,
    toParties: string[],
    roundNumber: number,
    payload: Buffer
  ): Promise<string> {
    if (!this.client) {
      throw new Error('Not connected');
    }

    return new Promise((resolve, reject) => {
      (this.client as grpc.Client & { routeMessage: (req: unknown, callback: (err: Error | null, res: { message_id: string }) => void) => void })
        .routeMessage(
          {
            session_id: sessionId,
            from_party: fromParty,
            to_parties: toParties,
            round_number: roundNumber,
            payload: payload,
          },
          (err: Error | null, response: { message_id: string }) => {
            if (err) {
              reject(err);
            } else {
              resolve(response.message_id);
            }
          }
        );
    });
  }

  /**
   * 报告完成
   */
  async reportCompletion(sessionId: string, partyId: string, publicKey: Buffer): Promise<boolean> {
    if (!this.client) {
      throw new Error('Not connected');
    }

    return new Promise((resolve, reject) => {
      (this.client as grpc.Client & { reportCompletion: (req: unknown, callback: (err: Error | null, res: { all_completed: boolean }) => void) => void })
        .reportCompletion(
          {
            session_id: sessionId,
            party_id: partyId,
            public_key: publicKey,
          },
          (err: Error | null, response: { all_completed: boolean }) => {
            if (err) {
              reject(err);
            } else {
              resolve(response.all_completed);
            }
          }
        );
    });
  }

  /**
   * 获取已注册的参与方列表
   * @param roleFilter 可选，按角色过滤 (persistent/delegate/temporary)
   * @param onlyOnline 可选，只返回在线的参与方
   */
  async getRegisteredParties(roleFilter?: string, onlyOnline?: boolean): Promise<RegisteredParty[]> {
    if (!this.client) {
      throw new Error('Not connected');
    }

    return new Promise((resolve, reject) => {
      (this.client as grpc.Client & { getRegisteredParties: (req: unknown, callback: (err: Error | null, res: GetRegisteredPartiesResponse) => void) => void })
        .getRegisteredParties(
          {
            role_filter: roleFilter || '',
            only_online: onlyOnline || false,
          },
          (err: Error | null, response: GetRegisteredPartiesResponse) => {
            if (err) {
              reject(err);
            } else {
              // 转换字段名从 snake_case 到 camelCase
              const parties = (response.parties || []).map((p: { party_id?: string; partyId?: string; role?: string; party_role?: string; online?: boolean; registered_at?: string; registeredAt?: string; last_seen_at?: string; lastSeenAt?: string }) => ({
                partyId: p.party_id || p.partyId || '',
                role: p.role || p.party_role || '',
                online: p.online || false,
                registeredAt: p.registered_at || p.registeredAt || '',
                lastSeenAt: p.last_seen_at || p.lastSeenAt || '',
              }));
              resolve(parties);
            }
          }
        );
    });
  }
}
