import { LiveAnnouncer } from '@angular/cdk/a11y';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { MatChipInputEvent, MatChipsModule } from '@angular/material/chips';
import { MatDialogRef, MAT_DIALOG_DATA, MatDialogModule } from '@angular/material/dialog';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatCheckboxModule } from '@angular/material/checkbox';
import { MatDividerModule } from '@angular/material/divider';
import { MatSelectModule } from '@angular/material/select';
import { MatIconModule } from '@angular/material/icon';
import { MatTooltipModule } from '@angular/material/tooltip';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
import { JobRestartPolicy, VideoCodec } from '@livekit/protocol';
import { LocalParticipant } from 'livekit-client';

import {
  EgressClient,
  EncodedFileOutput,
  EncodedFileType,
  EncodedOutputs,
  IngressInfo,
  IngressInput,
  IngressVideoEncodingPreset,
  Room,
  RoomCompositeOptions,
  RoomServiceClient,
  SegmentedFileOutput,
  SegmentedFileProtocol,
  StreamOutput,
  StreamProtocol,
} from 'livekit-server-sdk';
import {
  RoomApiService,
} from 'src/app/services/room-api.service';

@Component({
    selector: 'app-room-api-dialog',
    templateUrl: './room-api-dialog.component.html',
    styleUrl: './room-api-dialog.component.css',
    changeDetection: ChangeDetectionStrategy.Eager,
    imports: [FormsModule, MatDialogModule, MatFormFieldModule, MatInputModule, MatButtonModule, MatCheckboxModule, MatDividerModule, MatSelectModule, MatChipsModule, MatIconModule, MatTooltipModule, MatSlideToggleModule],
})
export class RoomApiDialogComponent {
  room: Room;
  localParticipant: LocalParticipant;
  roomServiceClient: RoomServiceClient;
  egressClient: EgressClient;

  apiRoomName: string;
  apiParticipantIdentity: string;
  apiTrackSid: string;
  muteTrack: boolean = true;

  egressRoomName: string;
  egressId: string;
  audioTrackId: string;
  videoTrackId: string;

  ROOM_COMPOSITE_LAYOUTS = ['grid', 'speaker', 'single-speaker'];
  roomCompositeLayoutSelected: string = 'grid';
  roomCompositeAudioOnly: boolean = false;
  roomCompositeVideoOnly: boolean = false;

  fileOutputSelected: boolean = true;
  streamOutputSelected: boolean = false;
  s3Endpoint: string = 'http://localhost:9100'; // 'http://minio:9000'
  rtmpUrls: string[] = ['rtmp://172.17.0.1:1936/live/'];
  segmentOutputSelected: boolean = false;
  segmentDuration: number = 6;

  ingressRoomName: string;
  ingressId: string;
  inputTypeSelected: IngressInput = IngressInput.URL_INPUT;
  ingressWithVideo: boolean = true;
  ingressWithAudio: boolean = false;
  ingressVideoCodecSelected: VideoCodec = VideoCodec.H264_BASELINE;
  ingressSimulcast: boolean = true;
  ingressEnableTranscoding: boolean = false;
  ingressVideoEncodingPresetSelected?: IngressVideoEncodingPreset = undefined;

  INGRESS_URL_TYPES: string[] = ['HTTP', 'SRT', 'RTSP'];
  ingressUrlType: string = 'HTTP';
  ingressUrlUri: string;

  agentRoomName: string;
  agentName: string = 'speech-processing';
  agentDispatchId: string;
  agentMetadata: string;
  agentRestartPolicySelected: JobRestartPolicy = JobRestartPolicy.JRP_ON_FAILURE;

  AGENT_RESTART_POLICIES: { value: JobRestartPolicy; viewValue: string }[] = [
    { value: JobRestartPolicy.JRP_ON_FAILURE, viewValue: 'On failure' },
    { value: JobRestartPolicy.JRP_NEVER, viewValue: 'Never' },
  ];

  response: string;

  INGRESS_INPUT_TYPES: { value: IngressInput; viewValue: string }[] = [
    { value: IngressInput.URL_INPUT, viewValue: 'URL' },
    { value: IngressInput.RTMP_INPUT, viewValue: 'RTMP' },
    { value: IngressInput.WHIP_INPUT, viewValue: 'WHIP' },
  ];
  INGRESS_VIDEO_CODECS: { value: VideoCodec; viewValue: string }[] = [
    { value: VideoCodec.H264_BASELINE, viewValue: 'H264' },
    { value: VideoCodec.VP8, viewValue: 'VP8' },
  ];
  INGRESS_VIDEO_ENCODING_PRESETS: {
    value: IngressInput | undefined;
    viewValue: string;
  }[] = [{ value: undefined, viewValue: 'undefined' }].concat(
    Object.keys(IngressVideoEncodingPreset)
      .filter((key) => isNaN(Number(key)))
      .map((key) => {
        return {
          value: IngressVideoEncodingPreset[key as any],
          viewValue: key.toString(),
        } as any;
      })
  );

  // s3Config = {
  //     endpoint: this.s3Endpoint,
  //     metadata: { "mytag": "myvalue" },
  //     tagging: "mytagging"
  // };

  announcer = inject(LiveAnnouncer);
  addOnBlur = true;
  readonly separatorKeysCodes = [ENTER, COMMA] as const;

  private data = inject(MAT_DIALOG_DATA);

  constructor(
    private roomApiService: RoomApiService,
    public dialogRef: MatDialogRef<RoomApiDialogComponent>,
  ) {
    const data = this.data;
    this.room = data.room;
    this.localParticipant = data.localParticipant;
    this.apiRoomName = this.room?.name;
    this.apiParticipantIdentity = this.localParticipant?.identity;
    this.apiTrackSid = this.localParticipant?.videoTrackPublications
      .values()
      .next().value?.trackSid!;
    this.egressRoomName = this.room?.name;
    this.audioTrackId = this.localParticipant?.audioTrackPublications
      .values()
      .next().value?.trackSid!;
    this.videoTrackId = this.localParticipant?.videoTrackPublications
      .values()
      .next().value?.trackSid!;
    this.ingressRoomName = this.room?.name;
    this.agentRoomName = this.room?.name;
  }

  async listRooms() {
    console.log('Listing rooms');
    try {
      const rooms = await this.roomApiService.listRooms();
      this.response = JSON.stringify(rooms, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async deleteRoom() {
    console.log('Deleting room');
    try {
      await this.roomApiService.deleteRoom(this.apiRoomName);
      this.response = 'Room deleted';
    } catch (error: any) {
      this.response = error;
      console.log(JSON.stringify(error));
    }
  }

  async deleteAllRooms() {
    console.log('Deleting all rooms');
    try {
      const promises: Promise<void>[] = [];
      const rooms = await this.roomApiService.listRooms();
      rooms.forEach((r) => {
        promises.push(this.roomApiService.deleteRoom(r.name));
      });
      await Promise.all(promises);
      this.response = 'Deleted ' + promises.length + ' rooms';
    } catch (error: any) {
      this.response = error;
    }
  }

  async listParticipants() {
    console.log('Listing participants');
    try {
      const participants = await this.roomApiService.listParticipants(
        this.apiRoomName
      );
      this.response = JSON.stringify(participants, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async getParticipant() {
    console.log('Getting participant');
    try {
      const participant = await this.roomApiService.getParticipant(
        this.apiRoomName,
        this.apiParticipantIdentity
      );
      this.response = JSON.stringify(participant, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async removeParticipant() {
    console.log('Removing participant');
    try {
      await this.roomApiService.removeParticipant(
        this.apiRoomName,
        this.apiParticipantIdentity
      );
      this.response = 'Participant removed';
    } catch (error: any) {
      this.response = error;
    }
  }

  async mutePublishedTrack() {
    console.log(`${this.muteTrack ? 'Muting' : 'Unmuting'} track`);
    try {
      await this.roomApiService.mutePublishedTrack(
        this.apiRoomName,
        this.apiParticipantIdentity,
        this.apiTrackSid,
        this.muteTrack
      );
      this.response = `Track ${this.muteTrack ? 'muted' : 'unmuted'}`;
      this.muteTrack = !this.muteTrack;
    } catch (error: any) {
      this.response = error;
    }
  }

  async listEgress() {
    console.log('Listing egress');
    try {
      const egress = await this.roomApiService.listEgress();
      this.response = JSON.stringify(egress, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async startRoomCompositeEgress() {
    console.log('Starting room composite egress');
    try {
      const encodedOutputs = this.getEncodedOutputs();
      const roomCompositeOptions: RoomCompositeOptions = {
        layout: this.roomCompositeLayoutSelected,
        audioOnly: this.roomCompositeAudioOnly,
        videoOnly: this.roomCompositeVideoOnly,
      };
      const egress = await this.roomApiService.startRoomCompositeEgress(
        this.egressRoomName,
        roomCompositeOptions,
        encodedOutputs
      );
      this.response = JSON.stringify(egress, null, 4);
      this.egressId = egress.egressId;
    } catch (error: any) {
      this.response = error;
    }
  }

  async startTrackCompositeEgress() {
    console.log('Starting track composite egress');
    try {
      const encodedOutputs = this.getEncodedOutputs();
      const egress = await this.roomApiService.startTrackCompositeEgress(
        this.egressRoomName,
        this.audioTrackId,
        this.videoTrackId,
        encodedOutputs
      );
      this.response = JSON.stringify(egress, null, 4);
      this.egressId = egress.egressId;
    } catch (error: any) {
      this.response = error;
    }
  }

  async startTrackEgress() {
    console.log('Starting track egress');
    try {
      const egress = await this.roomApiService.startTrackEgress(
        this.egressRoomName,
        !!this.audioTrackId ? this.audioTrackId : this.videoTrackId
      );
      this.response = JSON.stringify(egress, null, 4);
      this.egressId = egress.egressId;
    } catch (error: any) {
      this.response = error;
    }
  }

  async stopEgress() {
    console.log('Stopping egress');
    try {
      await this.roomApiService.stopEgress(this.egressId);
      this.response = 'Egress stopped';
    } catch (error: any) {
      this.response = error;
    }
  }

  async listIngress() {
    console.log('Listing ingress');
    try {
      const ingress = await this.roomApiService.listIngress();
      this.response = JSON.stringify(ingress, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async createIngress() {
    console.log('Creating ingress');
    try {
      const ingress = await this.roomApiService.createIngress(
        this.ingressRoomName,
        this.inputTypeSelected,
        this.ingressUrlType,
        this.ingressUrlUri,
        this.ingressWithAudio,
        this.ingressWithVideo,
        this.ingressVideoCodecSelected,
        this.ingressSimulcast,
        this.ingressEnableTranscoding,
        this.ingressVideoEncodingPresetSelected
      );
      this.response = JSON.stringify(ingress, null, 4);
      this.ingressId = ingress.ingressId;
    } catch (error: any) {
      this.response = error;
      console.warn(error);
      console.warn(error.code);
      console.warn(error.msg);
    }
  }

  async deleteIngress() {
    console.log('Deleting ingress');
    try {
      await this.roomApiService.deleteIngress(this.ingressId);
      this.response = 'Ingress deleted';
    } catch (error: any) {
      this.response = error;
    }
  }

  async deleteAllIngress() {
    console.log('Deleting all ingress');
    try {
      const promises: Promise<IngressInfo>[] = [];
      const ingresses = await this.roomApiService.listIngress();
      ingresses.forEach((i) => {
        promises.push(this.roomApiService.deleteIngress(i.ingressId));
      });
      await Promise.all(promises);
      this.response = 'Deleted ' + promises.length + ' ingresses';
    } catch (error: any) {
      this.response = error;
    }
  }

  async createDispatch() {
    console.log('Creating agent dispatch');
    try {
      const dispatch = await this.roomApiService.createDispatch(
        this.agentRoomName,
        this.agentName,
        this.agentMetadata || undefined,
        this.agentRestartPolicySelected
      );
      this.response = JSON.stringify(dispatch, null, 4);
      this.agentDispatchId = dispatch.id;
    } catch (error: any) {
      this.response = error;
    }
  }

  async getDispatch() {
    console.log('Getting agent dispatch');
    try {
      const dispatch = await this.roomApiService.getDispatch(
        this.agentDispatchId,
        this.agentRoomName
      );
      this.response = dispatch
        ? JSON.stringify(dispatch, null, 4)
        : 'Dispatch not found';
    } catch (error: any) {
      this.response = error;
    }
  }

  async listDispatch() {
    console.log('Listing agent dispatches');
    try {
      const dispatches = await this.roomApiService.listDispatch(
        this.agentRoomName
      );
      this.response = JSON.stringify(dispatches, null, 4);
    } catch (error: any) {
      this.response = error;
    }
  }

  async deleteDispatch() {
    console.log('Deleting agent dispatch');
    try {
      await this.roomApiService.deleteDispatch(
        this.agentDispatchId,
        this.agentRoomName
      );
      this.response = 'Agent dispatch deleted';
    } catch (error: any) {
      this.response = error;
    }
  }

  async deleteAllDispatch() {
    console.log('Deleting all agent dispatches');
    try {
      const promises: Promise<void>[] = [];
      const dispatches = await this.roomApiService.listDispatch(
        this.agentRoomName
      );
      dispatches.forEach((d) => {
        promises.push(
          this.roomApiService.deleteDispatch(d.id, this.agentRoomName)
        );
      });
      await Promise.all(promises);
      this.response = 'Deleted ' + promises.length + ' agent dispatches';
    } catch (error: any) {
      this.response = error;
    }
  }

  addRtmpUrl(event: MatChipInputEvent): void {
    const value = (event.value || '').trim();
    if (value) {
      this.rtmpUrls.push(value);
    }
    event.chipInput!.clear();
  }

  removeRtmpUrl(url: string): void {
    const index = this.rtmpUrls.indexOf(url);
    if (index >= 0) {
      this.rtmpUrls.splice(index, 1);
      this.announcer.announce(`Removed ${url}`);
    }
  }

  private getEncodedOutputs(): EncodedOutputs {
    // this.s3Config.endpoint = this.s3Endpoint;
    const encodedOutputs: EncodedOutputs = {};
    if (this.fileOutputSelected) {
      encodedOutputs.file = new EncodedFileOutput({
        fileType: EncodedFileType.DEFAULT_FILETYPE,
        filepath: 'room-composite-{room_id}-{room_name}-{time}',
      });
    }
    if (this.streamOutputSelected) {
      encodedOutputs.stream = new StreamOutput({
        urls: this.rtmpUrls,
        protocol: StreamProtocol.RTMP,
      });
    }
    if (this.segmentOutputSelected) {
      encodedOutputs.segments = new SegmentedFileOutput({
        protocol: SegmentedFileProtocol.HLS_PROTOCOL,
        segmentDuration: this.segmentDuration,
        // s3: this.s3Config
      });
    }
    return encodedOutputs;
  }
}
