import uuid from 'uuid-random';
import type { CallOptions } from '../call-options';
import { SDK_VERSION } from '../env';
import { TelnyxRTCMethod } from './methods';

type InviteACKMessage = {
  id: string;
  jsonrpc: '2.0';
  result: {
    callID: string;
    message: 'CALL CREATED';
    sessid: string;
  };
  voice_sdk_id: string;
};

type CreateInviteMessageParams = {
  sessionId: string;
  sdp: string;
  attach: boolean;
  callId: string;
  callOptions: CallOptions;
};

export function createInviteMessage({
  sdp,
  sessionId,
  attach,
  callId,
  callOptions,
}: CreateInviteMessageParams) {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.INVITE,
    params: {
      sessid: sessionId,
      sdp,
      'User-Agent': `ReactNative-${SDK_VERSION}`,
      dialogParams: {
        attach,
        callID: callId,
        audio: callOptions.audio,
        prefetchIceCandidates: callOptions.peerConnectionOptions?.prefetchIceCandidates,
        useTrickleIce: callOptions.peerConnectionOptions?.useTrickleIce,
        // Keep both keys for signaling compatibility. Android sends top-level
        // `trickle`; existing RN consumers read `useTrickleIce` from dialogParams.
        trickle: callOptions.peerConnectionOptions?.useTrickleIce,
        destination_number: callOptions.destinationNumber,
        remote_caller_id_name: callOptions.remoteCallerIdName,
        remote_caller_id_number: callOptions.remoteCallerIdNumber,
        caller_id_name: callOptions.callerIdName,
        caller_id_number: callOptions.callerIdNumber,
        custom_headers: callOptions.customHeaders,
        clientState: callOptions.clientState,
      },
    },
  };
}

export function isInviteACKMessage(msg: unknown): msg is InviteACKMessage {
  if (!msg) {
    return false;
  }
  const temp: Partial<InviteACKMessage> = msg;
  return (
    Boolean(temp.result?.callID) &&
    Boolean(temp.result?.sessid) &&
    temp.result?.message === 'CALL CREATED'
  );
}

type CandidateMessage = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.candidate';
  params: {
    sessid: string;
    candidate: string;
    sdpMid?: string | null;
    sdpMLineIndex?: number | null;
    dialogParams: {
      callID: string;
    };
  };
};

type CreateCandidateMessageParams = {
  sessionId: string;
  callId: string;
  candidate: string;
  sdpMid?: string | null;
  sdpMLineIndex?: number | null;
};

export function createCandidateMessage({
  sessionId,
  callId,
  candidate,
  sdpMid,
  sdpMLineIndex,
}: CreateCandidateMessageParams): CandidateMessage {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.CANDIDATE,
    params: {
      sessid: sessionId,
      candidate,
      sdpMid,
      sdpMLineIndex,
      dialogParams: {
        callID: callId,
      },
    },
  };
}

export type CandidateEvent = {
  id?: string | number;
  jsonrpc?: '2.0';
  method: 'telnyx_rtc.candidate';
  params: {
    sessid?: string;
    candidate: string;
    sdpMid?: string | null;
    sdpMLineIndex?: number | null;
    dialogParams?: {
      callID?: string;
      callId?: string;
    };
    callID?: string;
    callId?: string;
  };
  voice_sdk_id?: string;
};

export function getCallIdFromTrickleParams(params: {
  dialogParams?: { callID?: string; callId?: string };
  callID?: string;
  callId?: string;
}): string | undefined {
  return (
    params.dialogParams?.callID ??
    params.dialogParams?.callId ??
    params.callID ??
    params.callId
  );
}

export function isCandidateEvent(msg: unknown): msg is CandidateEvent {
  if (!msg) {
    return false;
  }
  const temp = msg as Partial<CandidateEvent>;
  const params = temp.params;
  const callId = params ? getCallIdFromTrickleParams(params) : undefined;
  return (
    temp.method === TelnyxRTCMethod.CANDIDATE &&
    typeof params?.candidate === 'string' &&
    params.sdpMid != null &&
    typeof params.sdpMLineIndex === 'number' &&
    Boolean(callId)
  );
}

type EndOfCandidatesMessage = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.endOfCandidates';
  params: {
    sessid: string;
    dialogParams: {
      callID: string;
    };
  };
};

type CreateEndOfCandidatesMessageParams = {
  sessionId: string;
  callId: string;
};

export function createEndOfCandidatesMessage({
  sessionId,
  callId,
}: CreateEndOfCandidatesMessageParams): EndOfCandidatesMessage {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.END_OF_CANDIDATES,
    params: {
      sessid: sessionId,
      dialogParams: {
        callID: callId,
      },
    },
  };
}

export type EndOfCandidatesEvent = {
  id?: string | number;
  jsonrpc?: '2.0';
  method: 'telnyx_rtc.endOfCandidates';
  params: {
    sessid?: string;
    dialogParams?: {
      callID?: string;
      callId?: string;
    };
    callID?: string;
    callId?: string;
  };
  voice_sdk_id?: string;
};

export function isEndOfCandidatesEvent(msg: unknown): msg is EndOfCandidatesEvent {
  if (!msg) {
    return false;
  }
  const temp = msg as Partial<EndOfCandidatesEvent>;
  const params = temp.params;
  const callId = params ? getCallIdFromTrickleParams(params) : undefined;
  return temp.method === TelnyxRTCMethod.END_OF_CANDIDATES && Boolean(callId);
}

export type RingingEvent = {
  id: number;
  jsonrpc: '2.0';
  method: 'telnyx_rtc.ringing';
  params: {
    callID: string;
    callee_id_name: string;
    callee_id_number: string;
    caller_id_name: string;
    caller_id_number: string;
    dialogParams: {
      custom_headers: { name: string; value: string }[];
    };
    display_direction: string;
    telnyx_leg_id: string;
    telnyx_session_id: string;
  };
  voice_sdk_id: string;
};

export function isRingingEvent(msg: unknown): msg is RingingEvent {
  if (!msg) {
    return false;
  }
  const temp: Partial<RingingEvent> = msg;
  return (
    temp.method === TelnyxRTCMethod.RINGING &&
    Boolean(temp.params?.callID) &&
    Boolean(temp.params?.telnyx_leg_id) &&
    Boolean(temp.params?.telnyx_session_id)
  );
}

export type AnswerEvent = {
  id: number;
  jsonrpc: '2.0';
  method: 'telnyx_rtc.answer';
  params: {
    callID: string;
    dialogParams: { custom_headers: { name: string; value: string }[] };
    sdp?: string; // Made optional since SDP might come via media event instead
    variables: {
      'Core-UUID': string;
    };
    /** The Telnyx call control ID for outbound flows (parked & bridged) */
    telnyx_call_control_id?: string;
    /** The Telnyx leg ID */
    telnyx_leg_id?: string;
    /** The Telnyx session ID */
    telnyx_session_id?: string;
  };
  voice_sdk_id: string;
};

export function isAnswerEvent(msg: unknown): msg is AnswerEvent {
  if (!msg) {
    return false;
  }
  const temp: Partial<AnswerEvent> = msg;
  return temp.method === TelnyxRTCMethod.ANSWER && Boolean(temp.params?.callID);
}

export function isByeEvent(msg: unknown): msg is ByeEvent {
  if (!msg) {
    return false;
  }
  const temp: Partial<ByeEvent> = msg;
  return (
    temp.method === TelnyxRTCMethod.BYE && Boolean(temp.params?.callID) && Boolean(temp.params?.cause)
  );
}

export type ByeEvent = {
  id: number;
  jsonrpc: '2.0';
  method: 'telnyx_rtc.bye';
  params: {
    callID: string;
    cause: string;
    causeCode: number;
    dialogParams: { custom_headers: { name: string; value: string }[] };
    sipCode: number;
    sipReason: string;
    sip_call_id: string;
  };
  voice_sdk_id: string;
};

type HangupRequest = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.bye';
  params: {
    sessid: string;
    dialogParams: {
      telnyxSessionId: string;
      telnyxLegId: string;
      callID: string;
      custom_headers?: { name: string; value: string }[];
    };
    cause: string;
    causeCode: number;
  };
};

type CreateHangupRequestParams = {
  sessionId: string;
  cause: string;
  causeCode: number;
  telnyxSessionId: string;
  telnyxLegId: string;
  callId: string;
  customHeaders?: { name: string; value: string }[];
};
export function createHangupRequest({
  cause,
  causeCode,
  sessionId,
  callId,
  telnyxLegId,
  telnyxSessionId,
  customHeaders,
}: CreateHangupRequestParams): HangupRequest {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.BYE,
    params: {
      sessid: sessionId,
      cause: cause,
      causeCode: causeCode,
      dialogParams: {
        telnyxSessionId,
        telnyxLegId,
        callID: callId,
        custom_headers: customHeaders || [],
      },
    },
  };
}

export type InviteEvent = {
  id: number;
  jsonrpc: '2.0';
  method: 'telnyx_rtc.invite';
  params: {
    callID: string;
    callee_id_name: string;
    callee_id_number: string;
    caller_id_name: string;
    caller_id_number: string;
    dialogParams: {
      custom_headers: { name: string; value: string }[];
    };
    display_direction: string;
    sdp?: string; // Made optional since SDP can come via media event
    telnyx_leg_id: string;
    telnyx_session_id: string;
  };
  voice_sdk_id: string;
};

export function isInviteEvent(msg: unknown): msg is InviteEvent {
  if (!msg) {
    return false;
  }
  const temp: Partial<InviteEvent> = msg;
  return temp.method === TelnyxRTCMethod.INVITE && Boolean(temp.params?.callID);
}

type AnswerMessage = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.answer';
  params: {
    sessid: string;
    sdp: string;
    dialogParams: {
      telnyxSessionId: string;
      telnyxLegId: string;
      callID: string;
      custom_headers?: { name: string; value: string }[];
    };
    'User-Agent': string;
  };
};

type CreateAnswerMessageParams = {
  sessionId: string;
  sdp: string;
  dialogParams: object;
  callId: string;
  telnyxSessionId: string;
  telnyxLegId: string;
  customHeaders?: { name: string; value: string }[];
};
export function createAnswerMessage({
  dialogParams,
  sdp,
  sessionId,
  callId,
  telnyxLegId,
  telnyxSessionId,
  customHeaders,
}: CreateAnswerMessageParams): AnswerMessage {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.ANSWER,
    params: {
      sessid: sessionId,
      sdp,
      dialogParams: {
        callID: callId,
        telnyxLegId,
        telnyxSessionId,
        custom_headers: customHeaders || [],
        ...dialogParams,
      },
      'User-Agent': `ReactNative-${SDK_VERSION}`,
    },
  };
}

type ModifyCallRequest = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.modify';
  params: {
    sessid: string;
    action: 'hold' | 'unhold';
    dialogParams: object;
  };
};

export function createModifyCallRequest({
  sessionId,
  action,
  callId,
}: {
  callId: string;
  sessionId: string;
  action: 'hold' | 'unhold';
}): ModifyCallRequest {
  return {
    jsonrpc: '2.0',
    id: uuid(),
    method: TelnyxRTCMethod.MODIFY,
    params: {
      sessid: sessionId,
      action,
      dialogParams: { callId },
    },
  };
}

type ModifyCallAnswer = {
  id: string;
  jsonrpc: '2.0';
  result: {
    action: 'hold' | 'unhold';
    callID: string;
    holdState: 'held' | 'unheld';
    sessid: string;
  };
  voice_sdk_id: string;
};

export function isModifyCallAnswer(msg: unknown): msg is ModifyCallAnswer {
  if (!msg) {
    return false;
  }
  const temp: Partial<ModifyCallAnswer> = msg;
  return (
    Boolean(temp.result?.callID) &&
    Boolean(temp.result?.sessid) &&
    (temp.result?.holdState === 'held' || temp.result?.holdState === 'unheld')
  );
}

/**
 * Verto `telnyx_rtc.info` request used to send DTMF tones.
 *
 * Mirrors the shape produced by the Android Voice SDK so the gateway sees a
 * consistent payload across platforms — including the full `dialogParams`
 * object with empty-but-present defaults for fields we don't track.
 */
type DTMFRequest = {
  jsonrpc: '2.0';
  id: string;
  method: 'telnyx_rtc.info';
  params: {
    sessid: string;
    dtmf: string;
    dialogParams: {
      attach: boolean;
      audio: boolean;
      callID: string;
      caller_id_name: string;
      caller_id_number: string;
      clientState: string;
      custom_headers: { name: string; value: string }[];
      destination_number: string;
      remote_caller_id_name: string;
      screenShare: boolean;
      useStereo: boolean;
      userVariables: unknown[];
      video: boolean;
    };
  };
};

type CreateDTMFRequestParams = {
  digits: string;
  sessionId: string;
  callId: string;
  callerIdName?: string;
  callerIdNumber?: string;
  clientState?: string;
  customHeaders?: { name: string; value: string }[];
  destinationNumber?: string;
  remoteCallerIdName?: string;
};

export function createDTMFRequest(params: CreateDTMFRequestParams): DTMFRequest {
  return {
    id: uuid(),
    jsonrpc: '2.0',
    method: TelnyxRTCMethod.INFO,
    params: {
      sessid: params.sessionId,
      dtmf: params.digits,
      dialogParams: {
        attach: false,
        audio: true,
        callID: params.callId,
        caller_id_name: params.callerIdName ?? '',
        caller_id_number: params.callerIdNumber ?? '',
        clientState: params.clientState ?? '',
        custom_headers: params.customHeaders ?? [],
        destination_number: params.destinationNumber ?? '',
        remote_caller_id_name: params.remoteCallerIdName ?? '',
        screenShare: false,
        useStereo: false,
        userVariables: [],
        video: false,
      },
    },
  };
}

type DTMFResponse = {
  id: string;
  jsonrpc: '2.0';
  // `message` is legacy — the Telnyx gateway currently acks DTMF with just `sessid`.
  // Kept optional so older servers that still emit `{ message: 'SENT', sessid }` still type-check.
  result: { message?: string; sessid: string };
  voice_sdk_id?: string;
};

export function isDTMFResponse(msg: unknown): msg is DTMFResponse {
  if (!msg || typeof msg !== 'object') {
    return false;
  }
  const temp = msg as Partial<DTMFResponse>;
  // A DTMF ack is any JSON-RPC success response for a `telnyx_rtc.info` request
  // that echoes back the session id. The Android SDK's `Call.dtmf()` doesn't
  // validate the response at all (fire-and-forget); this check is just enough
  // to distinguish the success ack from a JSON-RPC error frame.
  return Boolean(temp.result?.sessid);
}
