import { Peer as PeerJs } from "peerjs";
import type { DataConnection, MediaConnection } from "peerjs";
import { fileMgrInstance, type FileData, type FileInfo } from "./fileMgr";
import {
  NEED_CHUNK_FILE_SIZE,
  NEED_CHUNK_FILE_SIZE_PREVIEW,
  TransferStatus,
  TransferTask,
} from "./fileTransfer";
import { message, notification } from "ant-design-vue";
import {
  randomChars,
  sign2peerid,
  getPermissionSet,
  confirmWin,
} from "./common";
import { fetchTurnServers } from "./turn";

type MediaType = "desktop" | "call" | "camera";
// 发送超时时间（毫秒）
const SEND_TIMEOUT = 30000;

class Peer extends EventTarget {
  peer: PeerJs;
  //被动连接
  connections: Map<string, DataConnection> = new Map();
  //主动连接
  remoteConnection: DataConnection | null = null;
  //我的id
  id: string | null = null;
  //自定义标识
  sign: string | null = null;
  // 媒体流相关
  mediaConnections: Map<
    string,
    {
      connection: MediaConnection;
      remoteStream: MediaStream | null;
      localStream: MediaStream | null;
      type: MediaType;
    }
  > = new Map();
  constructor(sign: string) {
    super();
    this.sign = sign;
  }
  public async init() {
    let iceServers: {
      urls: string | string[];
      username?: string;
      credential?: string;
    }[] = [{ urls: "stun:8.134.35.244:3478" }];

    try {
      const turnServers = await fetchTurnServers(this.sign);
      if (turnServers.length > 0) {
        iceServers = iceServers.concat(turnServers);
      }
    } catch (error) {
      console.warn("获取 TURN 服务器失败，仅使用 STUN:", error);
    }

    this.peer = new PeerJs(sign2peerid(this.sign), {
      config: {
        iceServers,
      },
    });
    this.peer.on("open", (id) => {
      console.log("peer open", id);
      this.id = id;
      this.dispatchEvent(new CustomEvent("open", { detail: this.sign }));
    });

    this.peer.on("connection", (conn) => {
      console.log("peer connection", conn);
      this.setupConnection(conn);
    });

    this.peer.on("error", (err) => {
      console.log("peer error", err);
      this.dispatchEvent(new CustomEvent("error", { detail: err }));
    });

    this.peer.on("close", () => {
      console.log("peer close");
      this.dispatchEvent(new CustomEvent("close"));
    });

    this.peer.on("disconnected", () => {
      console.log("peer disconnected");
      this.dispatchEvent(new CustomEvent("disconnected"));
    });

    // 处理媒体连接
    this.peer.on("call", async (call) => {
      //弹出确认框
      const mediaType = call.metadata?.type;
      const type: MediaType =
        mediaType === "desktop" || mediaType === "camera" ? mediaType : "call";
      const title =
        type === "desktop"
          ? "屏幕共享请求"
          : type === "camera"
            ? "摄像头请求"
            : "语音通话请求";
      const mediaName =
        type === "desktop"
          ? "屏幕共享"
          : type === "camera"
            ? "摄像头"
            : "语音通话";
      const content = `${call.peer} 请求与您进行${mediaName}，是否接受？`;
      const permission =
        type === "desktop"
          ? Permission.desktop
          : type === "camera"
            ? Permission.camera
            : Permission.call;

      // 保存 this 引用
      const self = this;
      let handled = false;
      const onOk = async () => {
        if (handled) return;
        handled = true;
        try {
          let stream: MediaStream;

          // 根据call.metadata判断是桌面共享还是音视频通话
          if (type === "desktop") {
            stream = await navigator.mediaDevices.getDisplayMedia({
              video: true,
              audio: true,
            });
          } else if (type === "camera") {
            stream = await navigator.mediaDevices.getUserMedia({
              video: true,
              audio: false,
            });
          } else {
            stream = await navigator.mediaDevices.getUserMedia({
              audio: true,
              video: false,
            });
          }

          call.answer(stream);
          self.setupMediaConnection(
            call,
            type,
            stream,
            type !== "desktop" && type !== "camera",
          );

          self.dispatchEvent(
            new CustomEvent(
              type === "desktop"
                ? "desktop-started"
                : type === "camera"
                  ? "camera-started"
                  : "call-started",
              {
                detail: {
                  peerId: call.peer,
                  role:
                    type === "desktop"
                      ? "sharer"
                      : type === "camera"
                        ? "sharer"
                        : "caller",
                },
              },
            ),
          );
        } catch (error) {
          console.error("获取媒体设备失败:", error);
          self.dispatchEvent(
            new CustomEvent("error", {
              detail:
                "无法访问" +
                (type === "desktop"
                  ? "屏幕"
                  : type === "camera"
                    ? "摄像头"
                    : "麦克风"),
            }),
          );
          message.error(
            "无法访问" +
              (type === "desktop"
                ? "屏幕"
                : type === "camera"
                  ? "摄像头"
                  : "麦克风"),
          );
        }
      };
      const onReject = () => {
        if (handled) return;
        handled = true;
        call.close();
        message.info("已拒绝" + mediaName + "请求");
      };
      if (getPermissionSet()[permission]) {
        await onOk();
      } else if (type === "call" || type === "camera") {
        this.dispatchEvent(
          new CustomEvent("media-request", {
            detail: {
              peerId: call.peer,
              type,
              accept: onOk,
              reject: onReject,
            },
          }),
        );
      } else {
        confirmWin(title, content, "接受", "拒绝")
          .then(async () => {
            await onOk();
          })
          .catch(() => {
            onReject();
          });
      }
    });
  }

  private setupConnection(conn: DataConnection) {
    this.connections.set(conn.peer, conn);

    conn.on("data", (data: unknown) => {
      const typedData = data as Message;

      if (this.callbackMap.has(typedData.id)) {
        this.callbackMap.get(typedData.id)?.(typedData);
        return;
      }

      this.handleMessage(typedData, conn);

      this.dispatchEvent(
        new CustomEvent(typedData.type, {
          detail: { peerId: conn.peer, data: typedData },
        }),
      );
    });

    conn.on("open", () => {
      this.send(
        {
          type: MessageType.exchange_sign,
          data: this.sign,
        },
        conn,
        true,
      ).catch(() => {});
      this.dispatchEvent(
        new CustomEvent("connection-open", {
          detail: { peer: conn.peer, conn: conn },
        }),
      );
    });
    conn.on("close", () => {
      this.connections.delete(conn.peer);
      this.dispatchEvent(
        new CustomEvent("peer-disconnected", {
          detail: { peer: conn.peer, conn: conn },
        }),
      );
    });
  }

  connect(id: string) {
    const conn = this.peer.connect(sign2peerid(id));
    this.setupConnection(conn);
    this.remoteConnection = conn;
    this.checkConnection();
    return conn;
  }

  async requestDesktop(id: string) {
    if (!this.remoteConnection) {
      notification.error({
        message: "请创建连接",
      });
      return;
    }

    try {
      const offerStream = this.createDesktopOfferStream();

      const call = this.peer.call(sign2peerid(id), offerStream, {
        metadata: { type: "desktop" },
      });

      this.setupMediaConnection(call, "desktop", offerStream);

      this.dispatchEvent(
        new CustomEvent("desktop-started", {
          detail: {
            peerId: call.peer,
            role: "viewer",
          },
        }),
      );
    } catch (error) {
      console.error("创建连接失败:", error);
      notification.error({
        message: "创建连接失败",
      });
    }
  }

  private createDesktopOfferStream() {
    const canvas = document.createElement("canvas");
    canvas.width = 1;
    canvas.height = 1;
    const context = canvas.getContext("2d");
    context?.fillRect(0, 0, 1, 1);

    const stream = canvas.captureStream(1);
    const [track] = stream.getVideoTracks();
    if (!track) {
      throw new Error("无法创建桌面协商视频轨");
    }
    track.enabled = false;
    return stream;
  }

  private createCameraOfferStream() {
    const canvas = document.createElement("canvas");
    canvas.width = 1;
    canvas.height = 1;
    const context = canvas.getContext("2d");
    context?.fillRect(0, 0, 1, 1);

    const stream = canvas.captureStream(1);
    const [track] = stream.getVideoTracks();
    if (!track) {
      throw new Error("无法创建摄像头协商视频轨");
    }
    track.enabled = false;
    return stream;
  }

  async requestCall(id: string) {
    if (!this.remoteConnection) {
      notification.error({
        message: "请创建连接",
      });
      return;
    }

    try {
      const stream = await navigator.mediaDevices.getUserMedia({
        audio: true,
        video: false,
      });

      const call = this.peer.call(sign2peerid(id), stream, {
        metadata: { type: "call" },
      });

      this.setupMediaConnection(call, "call", stream);

      this.dispatchEvent(
        new CustomEvent("call-started", {
          detail: {
            peerId: call.peer,
          },
        }),
      );
    } catch (error) {
      console.error("获取音频设备失败:", error);
      notification.error({
        message: "无法访问麦克风",
      });
    }
  }

  async requestCamera(id: string) {
    if (!this.remoteConnection) {
      notification.error({
        message: "请创建连接",
      });
      return;
    }

    try {
      const offerStream = this.createCameraOfferStream();

      const call = this.peer.call(sign2peerid(id), offerStream, {
        metadata: { type: "camera" },
      });

      this.setupMediaConnection(call, "camera", offerStream);

      this.dispatchEvent(
        new CustomEvent("camera-started", {
          detail: {
            peerId: call.peer,
            role: "viewer",
          },
        }),
      );
    } catch (error) {
      console.error("创建摄像头连接失败:", error);
      notification.error({
        message: "创建摄像头连接失败",
      });
    }
  }

  // 结束媒体连接
  endMedia(peerId?: string, type?: MediaType) {
    if (peerId) {
      const mediaTypes: MediaType[] = type
        ? [type]
        : ["desktop", "call", "camera"];
      mediaTypes.forEach((mediaType) => {
        this.endMediaConnection(peerId, mediaType);
        if (this.remoteConnection) {
          this.send(
            {
              type:
                mediaType === "desktop"
                  ? MessageType.end_desktop
                  : mediaType === "camera"
                    ? MessageType.end_camera
                    : MessageType.end_call,
              data: {
                peerId,
              },
            },
            this.remoteConnection,
          ).catch((error) => {
            console.warn("send end media message error:", error);
          });
        }
      });
    } else {
      // 如果没有指定 peerId，结束所有连接
      this.endAllMediaConnections();
    }
  }

  private getMediaKey(peerId: string, type: MediaType) {
    return `${peerId}:${type}`;
  }

  private getMediaConnection(peerId: string, type: MediaType) {
    return this.mediaConnections.get(this.getMediaKey(peerId, type));
  }

  hasMedia(peerId: string, type: MediaType) {
    return !!this.getMediaConnection(peerId, type);
  }

  private updateMediaConnection(
    peerId: string,
    type: MediaType,
    data: Partial<{
      connection: MediaConnection;
      remoteStream: MediaStream | null;
      localStream: MediaStream | null;
    }>,
  ) {
    const key = this.getMediaKey(peerId, type);
    const current = this.mediaConnections.get(key);
    const connection = data.connection || current?.connection;
    if (!connection) return;
    this.mediaConnections.set(key, {
      connection,
      remoteStream:
        data.remoteStream !== undefined
          ? data.remoteStream
          : current?.remoteStream || null,
      localStream:
        data.localStream !== undefined
          ? data.localStream
          : current?.localStream || null,
      type,
    });
  }

  private callbackMap: Map<string, (data: any) => void> = new Map();
  async send(
    data: Message,
    conn: DataConnection = this.remoteConnection,
    isHandleResponse: boolean = false,
  ): Promise<any> {
    data.id = data.id || uuidv4();
    if (!conn) {
      notification.error({
        message: "请创建连接",
      });
      return Promise.reject("连接不存在");
    }
    if (!conn.open) {
      notification.error({
        message: "连接未打开",
        description: "WebRTC 数据通道已关闭或尚未建立",
      });
      return Promise.reject(new Error("连接未打开"));
    }
    try {
      const sendResult = conn.send(data);
      if (sendResult instanceof Promise) {
        await sendResult;
      }
    } catch (error) {
      notification.error({
        message: "发送失败",
        description: error instanceof Error ? error.message : String(error),
      });
      return Promise.reject(error);
    }
    if (isHandleResponse) {
      return Promise.resolve(data.data);
    } else {
      return new Promise((resolve, reject) => {
        const timeoutId = setTimeout(() => {
          this.callbackMap.delete(data.id);
          notification.error({
            message: "返回超时",
            description: "对方可能已离线或网络异常",
          });
          reject(new Error("返回超时"));
        }, SEND_TIMEOUT);
        this.callbackMap.set(data.id, (data: Message) => {
          clearTimeout(timeoutId);
          if (
            data.type === MessageType.response_getFile &&
            data.data.sendType == "chunk"
          ) {
            //分片传输 特殊处理 可以长时间等待进度
            return;
          }
          if (data.type === MessageType.error) {
            reject(data.data);
          } else {
            resolve(data.data);
          }
          this.callbackMap.delete(data.id);
        });
      });
    }
  }
  /**共交换的字节数 */
  public transbytesNum: number = 0;
  /**共交换的包数 */
  public transpackNum: number = 0;
  private checkConnection() {
    if (!this.remoteConnection || !this.remoteConnection.peerConnection) {
      return;
    }
    this.remoteConnection.peerConnection
      .getStats()
      .then((stats) => {
        stats.forEach((stat) => {
          if (stat.type == "data-channel") {
            //流量
            this.transbytesNum = stat.bytesReceived + stat.bytesSent;
            //包数
            this.transpackNum = stat.messagesReceived + stat.messagesSent;
          }
        });
      })
      .catch((err) => {
        console.warn("checkConnection getStats error:", err);
      });
    setTimeout(() => {
      this.checkConnection();
    }, 1000);
  }

  // 便捷方法用于添加事件监听器
  on(event: string, callback: EventListener) {
    this.addEventListener(event, callback);
  }

  // 便捷方法用于移除事件监听器
  off(event: string, callback: EventListener) {
    this.removeEventListener(event, callback);
  }

  private setupMediaConnection(
    call: MediaConnection,
    type: MediaType,
    localStream: MediaStream | null = null,
    acceptRemoteStream = true,
  ) {
    const peerId = call.peer;
    this.updateMediaConnection(peerId, type, {
      connection: call,
      localStream,
    });

    call.on("stream", (remoteStream: MediaStream) => {
      console.log("stream", remoteStream);
      if (!acceptRemoteStream) return;
      // 只有当流中包含相应类型的轨道时才保存连接
      const hasVideo = remoteStream.getVideoTracks().length > 0;
      const hasAudio = remoteStream.getAudioTracks().length > 0;

      if (
        ((type === "desktop" || type === "camera") && hasVideo) ||
        (type === "call" && hasAudio)
      ) {
        this.updateMediaConnection(peerId, type, {
          connection: call,
          remoteStream,
        });

        this.dispatchEvent(
          new CustomEvent("stream", {
            detail: {
              peerId,
              stream: remoteStream,
              type,
            },
          }),
        );
      }
    });

    call.on("close", () => {
      this.endMediaConnection(peerId, type);
    });
  }

  private endMediaConnection(peerId: string, type: MediaType) {
    const key = this.getMediaKey(peerId, type);
    const mediaConn = this.mediaConnections.get(key);
    if (mediaConn) {
      this.mediaConnections.delete(key);
      mediaConn.connection.close();
      mediaConn.remoteStream?.getTracks().forEach((track) => track.stop());
      mediaConn.localStream?.getTracks().forEach((track) => track.stop());

      this.dispatchEvent(
        new CustomEvent("media-ended", {
          detail: {
            peerId,
            type: mediaConn.type,
          },
        }),
      );
    }
  }

  // 结束所有媒体连接
  endAllMediaConnections() {
    for (const mediaConn of Array.from(this.mediaConnections.values())) {
      this.endMediaConnection(mediaConn.connection.peer, mediaConn.type);
    }
  }

  async handleMessage(data: Message, conn: DataConnection) {
    const remoteD = data.data;
    let file: FileInfo | null = null;
    if (data.type == MessageType.error) {
      console.error("handleMessage recive error", data.data);
      return;
    }
    let resData: Message = {
      type: MessageType.error,
      data: null,
      id: data.id,
    };
    try {
      // 权限校验
      /** 如果通过权限校验，则permissionNo为null */
      let permissionNo: Permission | null = null;
      for (const [permission, allowed] of Object.entries(getPermissionSet())) {
        const limit = PermissionLimit[
          permission as Permission
        ] as MessageType[];
        if (limit instanceof Array && limit.includes(data.type)) {
          if (allowed) {
            permissionNo = null;
            break;
          } else {
            permissionNo = permission as Permission;
          }
        }
      }
      if (permissionNo) {
        throw new Error(
          `没有权限执行该操作:${data.type},请检查权限${permissionNo}设置`,
        );
      }

      switch (data.type) {
        case MessageType.request_copyClipboard:
          resData.type = MessageType.response_copyClipboard;
          //读取粘贴板数据
          resData.data = await navigator.clipboard
            .readText()
            .then((text) => {
              return text;
            })
            .catch((err) => {
              return "没有粘贴板权限或窗口未聚焦，无法复制";
            });
          break;
        case MessageType.request_fileInfo:
          resData.type = MessageType.response_fileInfo;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          if (file) {
            await file.loadLocalDirectory();
            const json = file.toJson();
            resData.data = json;
          } else {
            resData.data = null;
          }
          break;
        case MessageType.request_fileSize:
          resData.type = MessageType.response_fileSize;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          resData.data = await file.getFileSize();
          break;
        case MessageType.request_deleteFile:
          resData.type = MessageType.response_deleteFile;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          await file.deleteItself();
          break;
        case MessageType.request_renameFile:
          resData.type = MessageType.response_renameFile;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          await file.renameFile(remoteD.newName);
          break;
        case MessageType.request_createDirectory:
          resData.type = MessageType.response_createDirectory;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          await file.createDirectory(remoteD.name);
          break;
        case MessageType.request_createFile:
          resData.type = MessageType.response_createFile;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          await file.createFile(remoteD.name, remoteD.buffer);
          break;
        case MessageType.request_saveFile:
          resData.type = MessageType.response_saveFile;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          await file.saveFile(remoteD.buffer);
          break;
        case MessageType.request_getFileLastModified:
          resData.type = MessageType.response_getFileLastModified;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          resData.data = await file.getFileLastModified();
          break;
        case MessageType.request_getFile:
          if (!remoteD.preView) {
            //特殊处理下载权限
            if (!getPermissionSet()[Permission.download]) {
              throw new Error(
                `没有权限执行该操作:${data.type},请检查权限${Permission.download}设置`,
              );
            }
          }
          resData.type = MessageType.response_getFile;
          file = (await fileMgrInstance.getRootFile()).getFileInfo(
            remoteD.path,
          );
          let fileData = (await file.getFile(remoteD.preView)) as FileData;
          if (fileData.size > NEED_CHUNK_FILE_SIZE) {
            this.send(
              {
                type: MessageType.response_getFile,
                data: {
                  sendType: "chunk",
                },
                id: data.id,
              },
              conn,
              true,
            );
            //分片传输
            if (remoteD.preView) {
              if (fileData.size < NEED_CHUNK_FILE_SIZE_PREVIEW) {
                await file.sendFileChunk(conn, remoteD.preView);
              }
            } else {
              await file.sendFileChunk(conn, false, remoteD.savePath);
            }
            fileData.buffer = null;
          } else {
            //直接传输
            file
              .getTransfer(conn)
              .addTask(new TransferTask(fileData, file))
              .updateFileData(fileData, TransferStatus.COMPLETED);
          }
          resData.data = fileData;
          break;
        case MessageType.push_file_chunk:
          resData.type = MessageType.response_push_file_chunk;
          resData.data = "ok";
          let fData = remoteD as FileData;
          if (fData.preView) {
            await fileMgrInstance.remoteRootFile
              .getFileInfo(fData.path)
              .getTransfer(conn)
              .receiveFile(fData, true);
          } else {
            await (await fileMgrInstance.getRootFile())
              .getTransfer(conn)
              .receiveFile(fData);
          }
          break;
        case MessageType.push_file_complete:
          resData.type = MessageType.response_push_file_complete;
          console.log("push_file_complete", remoteD);
          if (remoteD.preView) {
            notification.success({
              message: "文件缓存建立完成",
            });
          } else {
            notification.success({
              message: "文件流式传输完成",
              description: remoteD.name,
            });
          }
          resData.data = "ok";
          break;
        case MessageType.end_desktop:
        case MessageType.end_camera:
        case MessageType.end_call:
          this.endMediaConnection(
            conn.peer,
            data.type === MessageType.end_desktop
              ? "desktop"
              : data.type === MessageType.end_camera
                ? "camera"
                : "call",
          );
          break;
        case MessageType.exchange_sign:
          this.dispatchEvent(
            new CustomEvent("exchange-sign", {
              detail: { peerId: conn.peer, sign: remoteD },
            }),
          );
          break;
        default:
          resData.type = MessageType.error;
          resData.data = "未知消息类型";
          break;
      }
    } catch (err: any) {
      console.error("handleMessage error", err);
      resData.type = MessageType.error;
      resData.data = err.message || JSON.stringify(err);
    }
    this.send(resData, conn, true);
  }
}

export const peer = new Peer(randomChars(6));
export interface Message {
  type: MessageType;
  data: any;
  id?: string; //uuid
}
export enum MessageType {
  request_copyClipboard = "request_copyClipboard",
  response_copyClipboard = "response_copyClipboard",
  request_getFile = "request_getFile",
  response_getFile = "response_getFile",
  request_getFileLastModified = "request_getFileLastModified",
  response_getFileLastModified = "response_getFileLastModified",
  request_saveFile = "request_saveFile",
  response_saveFile = "response_saveFile",
  request_createFile = "request_createFile",
  response_createFile = "response_createFile",
  request_createDirectory = "request_createDirectory",
  response_createDirectory = "response_createDirectory",
  request_renameFile = "request_renameFile",
  response_renameFile = "response_renameFile",
  request_deleteFile = "request_deleteFile",
  response_deleteFile = "response_deleteFile",
  error = "handle_error",
  request_fileSize = "request_fileSize",
  response_fileSize = "response_fileSize",
  response_fileInfo = "response_fileInfo",
  request_fileInfo = "request_fileInfo",
  push_file_chunk = "push_file_chunk",
  push_file_complete = "push_file_complete",
  response_push_file_chunk = "response_push_file_chunk",
  response_push_file_complete = "response_push_file_complete",
  end_call = "end_call",
  end_desktop = "end_desktop",
  end_camera = "end_camera",
  exchange_sign = "exchange_sign",
}
export enum Permission {
  edit = "edit",
  view = "view",
  download = "download",
  desktop = "desktop",
  call = "call",
  camera = "camera",
}
export const PermissionLimit = {
  [Permission.edit]: [
    MessageType.request_saveFile,
    MessageType.request_createFile,
    MessageType.request_createDirectory,
    MessageType.request_renameFile,
    MessageType.request_deleteFile,
  ],
  [Permission.view]: [
    MessageType.request_copyClipboard,
    MessageType.request_getFile,
    MessageType.request_getFileLastModified,
    MessageType.request_fileInfo,
    MessageType.request_fileSize,
  ],
  [Permission.download]: [MessageType.request_getFile],
  [Permission.desktop]: false,
  [Permission.call]: false,
  [Permission.camera]: false,
};
export function uuidv4(): string {
  return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
    var r = (Math.random() * 16) | 0,
      v = c == "x" ? r : (r & 0x3) | 0x8;
    return v.toString(16);
  });
}
