import { Injectable, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
import { ClientGrpc, Client } from '@nestjs/microservices';
import { grpcClientOptions } from './grpc-scene.options';
import { Logger } from '@nestjs/common';
import { DataChannel } from 'node-datachannel';
import * as path from 'path';
import { BehaviorSubject } from 'rxjs';
// import * as streamBuffers from 'stream-buffers';
import { ActionType } from './actionType';
import { CacheService } from 'src/cache/cache.service';
import { StreamService } from './stream/stream.service';
import { InjectQueue } from '@nestjs/bull';
import { Queue } from 'bull';
import { RotateService } from 'src/rotate/rotate.service';
import { DelayQueue, RxQueue, ThrottleQueue, DebounceQueue } from 'rx-queue';

@Injectable()
export class SceneService implements OnModuleInit, OnModuleDestroy {
  constructor(
    private cacheService: CacheService,
    private streamService: StreamService,
    private rotateService: RotateService,
    @InjectQueue('rotate') private rotateQueue: Queue,
  ) { }
  @Client(grpcClientOptions) private readonly client: ClientGrpc;
  private sceneGrpcService: SceneGrpcService;

  private logger: Logger = new Logger('SceneService');
  private frameCntInterval = 1000;
  public _frameInteval: NodeJS.Timeout;
  public _frameTimeout: NodeJS.Timeout;
  private channel: DataChannel;
  public startSteaming = new BehaviorSubject<boolean>(false);
  private user_id: string;
  private roomId: string;
  private onSteaming = false;
  private testFrame = -1;
  private rotateframeCnt = -1;
  private mockserverTime = Date.now() - 1653000000478;
  private lastRenderMedia = '';
  private frameCnt = new BehaviorSubject<number>(-1);
  private frameCntSubscription: any;
  private roQueueSubscription: any;
  private streamServiceSub: any;
  private roQueue: RxQueue = new ThrottleQueue(50);
  private rotateTimeStamp: number;

  onModuleInit(): void {
    this.sceneGrpcService =
      this.client.getService<SceneGrpcService>('SceneGrpcService');
    this.logger.log('init SceneGrpcService');
    this.streamServiceSub = this.streamService.onSteaming.subscribe((val) => {
      this.onSteaming = val;
    });
    Number.prototype.padLeft = function (n, str) {
      return Array(n - String(this).length + 1).join(str || '0') + this;
    };
  }

  startStream(): void {
    clearInterval(this._frameInteval);
    if (this.frameCnt.value === -1) {
      this._frameInteval = setInterval(async () => {
        const next = this.frameCnt.value + 1;
        this.frameCnt.next(next);
      }, 1000);
    }
  }

  holdSteam(): void {
    clearInterval(this._frameInteval);
  }

  resumeStream(value: number) {
    this.frameCnt.next(value);
    clearTimeout(this._frameTimeout);
    clearInterval(this._frameInteval);
    this._frameTimeout = setTimeout(() => {
      this._frameInteval = setInterval(async () => {
        const next = this.frameCnt.value + 1;
        this.frameCnt.next(next);
      }, 1000);
    }, 1000);
  }

  stopStream(): void {
    if (this.frameCntSubscription) {
      this.frameCntSubscription.unsubscribe();
    }
    if (this.roQueueSubscription) {
      this.roQueueSubscription.unsubscribe();
    }
    this.frameCnt.next(-1);
    clearInterval(this._frameInteval);
    this.rotateframeCnt = -1;
  }

  setConfig(user_id: string, roomId: string) {
    this.user_id = user_id;
    this.roomId = roomId;
  }

  onModuleDestroy() {
    if ('unsubscribe' in this.streamServiceSub) {
      this.streamService.onSteaming.unsubscribe();
    }
  }

  getRoute(request: RouteRequest) {
    return this.sceneGrpcService.getRoute(request);
  }
  getBreakPoint(request: GetBreakPointRequest) {
    return this.sceneGrpcService.getBreakPoint(request);
  }

  init(request: InitRequest) {
    try {
      // const initReply = this.sceneGrpcService.init(request);
      // initReply.subscribe((reply) => {
      //   console.log('initReply', reply);
      // });
      this.rotateService.init(request.app_id, request.user_id);
    } catch (error) {
      console.log('error', error);
    }
  }

  exit(request: ExitRequest) {
    // const exitReply = this.sceneGrpcService.exit(request);
    // exitReply.subscribe((reply) => {
    //   console.log('exitReply', reply);
    // });
  }

  move(request: MoveRequest) {
    return this.sceneGrpcService.move(request);
  }

  async rotate(request: RotateRequest) {
    try {
      this.rotateQueue.add(request, {
        jobId: request.trace_id,
      });
      if (!this.onSteaming) {
        const redisMeta = await this.rotateService.rotate(request);
        if (redisMeta && 'mediaSrc' in redisMeta) {
          const mediaSrc: string = redisMeta.mediaSrc || '';
          if (mediaSrc.length > 0) {
            let src = mediaSrc.split('?')[0];
            // 临时本地替换路经
            src = src.replace('/0000000001/100/', '');
            // 判断不是同一条源时才推出
            if (this.lastRenderMedia !== src) {
              console.log('[media]', src);
              // console.log('不同源');
              // this.frameCnt += 1;
              this.holdSteam();
              this.lastRenderMedia = src;
              const clipPath = path.join(__dirname, `../ws/video/${src}`);
              // console.log('src-clipPath', src, clipPath);
              delete redisMeta.mediaSrc;
              const stream: StreamFrameType = {
                frame: -1,
                clipPath: clipPath,
                metaData: JSON.stringify(redisMeta),
                serverTime: this.mockserverTime,
                DIR: 3,
              };
              this.roQueue.next(stream);
            }
          }
        }
      }
    } catch (error) {
      this.logger.error('rotate', error);
      console.log('error', error);
    }
  }

  joystick(request: JoystickRequest) {
    return this.sceneGrpcService.joystick(request);
  }

  handleDataChanelOpen(channel: DataChannel): void {
    this.channel = channel;
    this.streamService.setChannel(channel);
    this.startSteaming.next(true);
    this.startStream();
    this.handleStream();
  }

  handleDataChanelClose(): void {
    this.stopStream();
    this.startSteaming.next(false);
    this.streamService.closeChannel();
    const exitRequest: ExitRequest = {
      action_type: 1002,
      user_id: this.user_id,
      trace_id: '',
    };
    this.exit(exitRequest);
  }

  handleMessage(message: string | Buffer) {
    try {
      if (typeof message === 'string') {
        // wasm:特例, requestIframe
        if (message.includes('wasm:')) {
          const msg: RTCMessageRequest = JSON.parse(
            message.replace('wasm:', ''),
          );
          if (msg.MstType === 0) {
            this.logger.log('lost I frame');
            this.handleIframeRequest();
          }
        } else {
          const msg: RTCMessageRequest = JSON.parse(message);
          switch (msg.action_type) {
            case ActionType.walk:
              const walk = msg;
              this.walking(walk);
              break;
            case ActionType.breathPoint:
              this.handleBreath(msg);
              break;
            case ActionType.rotate:
              const rotateRequest: RotateRequest = msg;
              this.rotate(rotateRequest);
              break;
            case ActionType.userStatus:
              this.updateUserStatus(msg);
              break;
            case ActionType.status:
              this.updateStatus();
              break;
            default:
              break;
          }
        }
      }
    } catch (error) {
      this.logger.error('handleMessage:rtc--error', message);
    }
  }

  handleIframeRequest() {
    // this.frameCnt += 1;
    // this.onSteaming = true;
    // const stream: StreamFrameType = {
    //   frame: this.frameCnt,
    //   clipPath: path.join(__dirname, '../ws/video/100/100.0000.h264'),
    //   metaData: JSON.stringify(frameMetaReply),
    //   serverTime: this.mockserverTime,
    // };
    // this.streamService.pushFrameToSteam(stream);
  }

  walking(request) {
    console.log('walking', request);
    console.log('walking-onSteaming', this.onSteaming);

    const walk1 = {
      traceIds: [request.trace_id],
      vehicle: null,
      newUserStates: [
        {
          userId: request.user_id,
          playerState: {
            roomTypeId: '',
            person: 0,
            avatarId: '',
            skinId: '',
            roomId: '',
            isHost: false,
            isFollowHost: false,
            skinDataVersion: '',
            avatarComponents: '',
            nickName: '',
            movingMode: 0,
            attitude: '',
            areaName: '',
            pathName: '',
            pathId: '',
            avatarSize: 1,
            extra: '',
            prioritySync: false,
            avatarURL: '',
            micStatus: 0,
            player: {
              position: { x: -755, y: -1450, z: -34 },
              angle: { pitch: 0, yaw: 348, roll: 4.5912e-41 },
            },
            camera: {
              position: { x: -1075, y: -1450, z: 86 },
              angle: { pitch: 0, yaw: 0, roll: 0 },
            },
            cameraCenter: { x: -755, y: -1450, z: -34 },
          },
          renderInfo: {
            renderType: 0,
            videoFrame: null,
            cameraStateType: 3,
            isMoving: 1,
            needIfr: 0,
            isVideo: 0,
            stillFrame: 0,
            isRotating: 0,
            isFollowing: 0,
            clientPanoTitlesBitmap: [],
            clientPanoTreceId: '',
            prefetchVideoId: '',
            noMedia: false,
          },
          event: null,
          relation: 1,
        },
      ],
      actionResponses: [
        {
          actionType: 1,
          pointType: 0,
          extra: '',
          traceId: request.trace_id,
          packetId: '',
          nps: [],
          peopleNum: 0,
          zoneId: '',
          echoMsg: '',
          reserveDetail: null,
          userWithAvatarList: [],
          newUserStates: [],
          code: 0,
          msg: '',
        },
      ],
      getStateType: 0,
      code: 0,
      msg: 'OK',
    };
    const nextframe = this.frameCnt.value + 1;

    const stream: StreamFrameType = {
      frame: nextframe,
      clipPath: path.join(__dirname, `../ws/video/2.h264`),
      metaData: JSON.stringify(walk1),
      serverTime: this.mockserverTime,
      DIR: 3,
    };
    console.log('walking', this.frameCnt, stream.clipPath);
    this.streamService.pushFrameToSteam(stream);
  }

  handleBreath(request) {
    // console.log('handleBreath', request);
    const nps = {
      actionType: 1004,
      pointType: 100,
      extra: '',
      traceId: request.trace_id,
      packetId: '',
      nps: [
        { x: 235, y: -1791.875, z: -0.25 },
        { x: 235, y: -1750, z: 0.5045047 },
        { x: 235, y: -1722.5, z: 1 },
        { x: 235, y: -1700, z: 1.4054055 },
        { x: 235, y: -1653.125, z: 2.25 },
        { x: 235, y: -1600, z: 3.2072072 },
        { x: 235, y: -1583.75, z: 3.5 },
        { x: 235, y: -1550, z: 4.108108 },
        { x: 235, y: -1514.375, z: 4.75 },
        { x: 235, y: -1499.9999, z: 5.0090113 },
        { x: 235, y: -1445, z: 6 },
        { x: 200, y: -1900, z: -1.8723404 },
        { x: 200, y: -1850, z: -0.80851054 },
        { x: 200, y: -1800, z: 0.23423433 },
        { x: 200, y: -1750, z: 1.1351352 },
        { x: 200, y: -1700, z: 2.036036 },
        { x: 200, y: -1650, z: 2.9369369 },
        { x: 200, y: -1600, z: 3.837838 },
        { x: 200, y: -1550, z: 4.738739 },
        { x: 200, y: -1500, z: 5.63964 },
        { x: 200, y: -1444.2554, z: 6 },
        { x: 176.25, y: -1443.75, z: 6 },
        { x: 150, y: -2000, z: -4 },
        { x: 150, y: -1950, z: -2.93617 },
        { x: 150, y: -1932.8572, z: -2.5714293 },
        { x: 150, y: -1900, z: -1.8723404 },
        { x: 150, y: -1865.7142, z: -1.1428561 },
        { x: 150, y: -1850.0001, z: -0.8085134 },
        { x: 150, y: -1798.5714, z: 0.28571463 },
        { x: 150, y: -1750, z: 1.319149 },
        { x: 150, y: -1731.4286, z: 1.7142854 },
        { x: 150, y: -1700, z: 2.382979 },
        { x: 150, y: -1664.2856, z: 3.142859 },
        { x: 150, y: -1650.0001, z: 3.446806 },
        { x: 150, y: -1597.1428, z: 4.5714293 },
        { x: 150, y: -1550, z: 5.5744677 },
        { x: 150, y: -1443.1917, z: 6 },
        { x: 150, y: -1500, z: 6 },
        { x: 150, y: -1530, z: 6 },
        { x: 117.5, y: -1442.5, z: 6 },
        { x: 115, y: -1495, z: 6 },
        { x: 100, y: -1442.1277, z: 6 },
        { x: 100, y: -1495, z: 6 },
        { x: 58.75, y: -1441.25, z: 6 },
        { x: 57.5, y: -1495, z: 6 },
        { x: 0, y: -1440, z: 6 },
        { x: 0, y: -1495, z: 6 },
        { x: -50, y: -1494.9999, z: 6 },
        { x: -50, y: -1439.3378, z: 7.324497 },
        { x: -64.16667, y: -1495, z: 6 },
        { x: -68.63635, y: -1439.091, z: 7.8181725 },
        { x: -100, y: -1494.9999, z: 6 },
        { x: -100, y: -1438.6755, z: 8.648995 },
        { x: -128.33334, y: -1495, z: 6 },
        { x: -137.2727, y: -1438.1818, z: 9.636378 },
        { x: -150, y: -1494.9999, z: 6 },
        { x: -150, y: -1438.0132, z: 9.973526 },
        { x: -192.50002, y: -1495, z: 6 },
        { x: -200, y: -1450, z: 7.936013 },
        { x: -205.90906, y: -1437.2727, z: 11.45455 },
        { x: -250, y: -1450, z: 9.084496 },
        { x: -250, y: -1436.6887, z: 12.622522 },
        { x: -256.6667, y: -1495, z: 6 },
        { x: -274.5454, y: -1436.3636, z: 13.272723 },
        { x: -300, y: -1494.9999, z: 6 },
        { x: -300, y: -1450, z: 10.232978 },
        { x: -300, y: -1436.0264, z: 13.947053 },
        { x: -320.83337, y: -1495, z: 6 },
        { x: -343.1818, y: -1435.4546, z: 15.090897 },
        { x: -350, y: -1494.9999, z: 6 },
        { x: -350, y: -1450, z: 11.38146 },
        { x: -385, y: -1495, z: 6 },
        { x: -400, y: -1450, z: 10.610336 },
        { x: -405, y: -1475, z: 6 },
        { x: -411.81818, y: -1434.5454, z: 11.454548 },
        { x: -450, y: -1474.9999, z: 0.8571429 },
        { x: -450, y: -1450, z: 3.8835106 },
        { x: -450, y: -1434.0398, z: 6.3973427 },
        { x: -475, y: -1475, z: -2 },
        { x: -480.45453, y: -1433.6364, z: 2.3636398 },
        { x: -500, y: -1474.9999, z: -4.8571415 },
        { x: -500, y: -1450, z: -2.8433151 },
        { x: -500, y: -1433.3776, z: -0.22517776 },
        { x: -545, y: -1475, z: -10 },
        { x: -549.0909, y: -1432.7273, z: -6.727272 },
        { x: -550, y: -1450, z: -9.570139 },
        { x: -600, y: -1450, z: -16.285715 },
        { x: -600, y: -1474.9999, z: -16.285715 },
        { x: -600, y: -1432.053, z: -13.4702 },
        { x: -615, y: -1475, z: -18 },
        { x: -617.7273, y: -1431.8182, z: -15.818193 },
        { x: -650, y: -1450, z: -22 },
        { x: -650, y: -1474.9999, z: -22 },
        { x: -650, y: -1431.3907, z: -20.09272 },
        { x: -685, y: -1475, z: -26 },
        { x: -686.36365, y: -1430.909, z: -24.909088 },
        { x: -700, y: -1450, z: -27.714287 },
        { x: -700, y: -1474.9999, z: -27.714283 },
        { x: -700, y: -1430.7284, z: -26.71522 },
        { x: -755, y: -1430, z: -34 },
        { x: -755, y: -1450, z: -34 },
        { x: -755, y: -1475, z: -34 },
      ],
      peopleNum: 0,
      zoneId: '',
      echoMsg: '',
      reserveDetail: null,
      userWithAvatarList: [],
      newUserStates: [],
      code: 0,
      msg: '',
    };
    this.streamService.pushNormalDataToStream(nps);
  }

  updateStatus() {
    const reply = {
      data: { action_type: 1009, echo_msg: { echoMsg: Date.now() } },
      track: false,
    };
    this.streamService.pushNormalDataToStream(reply);
  }
  async updateUserStatus(request) {
    try {
      //TODO 接入redis数据
      const redisMeta = await this.rotateService.getNewUserStateRequest(
        request,
      );
      if (redisMeta) {
        redisMeta.actionType = 1024;
        this.streamService.pushNormalDataToStream(redisMeta);
      }
    } catch (error) {
      this.logger.error('updateUserStatus::function', error);
    }
  }

  pushFirstRender(clipPath: string, metaData: string): Promise<boolean> {
    return new Promise<boolean>(async (resolve, reject) => {
      try {
        const streamData: StreamFrameType = {
          frame: 1,
          clipPath: clipPath,
          metaData: metaData,
          serverTime: this.mockserverTime,
        };
        const hasPush = await this.streamService.pushFrameToSteam(streamData);
        return resolve(hasPush);
      } catch (error) {
        return reject(false);
      }
    });
  }

  handleStream() {
    this.frameCntSubscription = this.frameCnt.subscribe(async (frame) => {
      try {
        console.log('frame', frame);
        if (frame === 1) {
          const redisData = await this.rotateService.echo(this.user_id);
          if (redisData && 'mediaSrc' in redisData) {
            const mediaSrc: string = redisData.mediaSrc || '';
            if (mediaSrc.length > 0) {
              let src = mediaSrc.split('?')[0];
              // 临时本地替换路经
              src = src.replace('/0000000001/100/', '');
              const clipPath = path.join(__dirname, `../ws/video/${src}`);
              delete redisData.mediaSrc;

              this.logger.log(
                `${this.user_id}:first render stream`,
                JSON.stringify({ path: clipPath, meta: redisData }),
              );

              this.holdSteam();
              const status = await this.pushFirstRender(
                clipPath,
                JSON.stringify(redisData),
              );
              if (status) {
                this.resumeStream(2);
              } else {
                this.logger.error('first render problem', status);
              }
            }
          }
        }
        if (frame > 1 && !this.onSteaming) {
          const redisDataAuto = await this.rotateService.echo(this.user_id);
          if (redisDataAuto) {
            const streamMeta: StreamMetaType = {
              frame: frame,
              metaData: JSON.stringify(redisDataAuto),
            };
            this.streamService.pushMetaDataToSteam(streamMeta);
          } else {
          }
        }
      } catch (error) {
        this.logger.error('handleStream', error);
      }
    });

    this.roQueueSubscription = this.roQueue.subscribe(
      async (stream: StreamFrameType) => {
        this.rotateTimeStamp = Date.now();

        if (this.rotateframeCnt === -1) {
          this.rotateframeCnt = this.frameCnt.value;
        }
        this.rotateframeCnt += 1;
        stream.frame = this.rotateframeCnt;
        console.log(
          'roQueueSubscription \n',
          this.rotateframeCnt,
          stream.metaData,
        );
        await this.streamService.pushFrameToSteam(stream);
        setTimeout(() => {
          const now = Date.now();
          if (now - this.rotateTimeStamp > 300) {
            const next = this.rotateframeCnt + 1;
            this.resumeStream(next);
            this.rotateframeCnt = -1;
          }
        }, 300);
      },
    );
  }
}
