import * as path from "path";
import * as url from "url";
import { BrowserWindow, ipcMain, screen, session } from "electron";
import type Electron from "electron";
import WindowBounds from "./WindowBounds";
import { MessageId } from "./types";
import { getDisplay } from "./screen";
import { storeToView } from "./store";
import {getPackageResourcesPath} from "./packageUtils";

export enum WindowId {
  Main = "mainWindow"
}

export type WindowRectChangedArgs = {
  windowId: string;
  displayId: number;
  rect: Electron.Rectangle;
};

export type WindowDisplayChangedArgs = {
  windowId: string;
  displayId: number;
};

export type WindowTitleArgs = {
  windowId: string;
  title: string;
};

export type ChildWindowOptions = {
  title?: string;
  isMainParent?: boolean;
  displayId?: number;
  rect?: Electron.Rectangle;
  maximizeOnExternalDisplay?: boolean;
};

const SSL_DISABLE_VERIFICATION = 0;
// const SSL_FAILURE = -2;
const SSL_USE_CHROME_VERIFICATION = -3;

class Window {
  public window?: BrowserWindow;
  public displayId?: number;
  public rect?: Electron.Rectangle;

  protected readonly windowBounds: WindowBounds;
  protected options: ChildWindowOptions = {};
  static readonly windows: Window[] = [];
  private static isStaticInitialized = false;

  constructor(readonly windowId: string, public mainWindow?: BrowserWindow) {
    Window.staticInit();
    this.windowBounds = new WindowBounds(this);
    Window.windows.push(this);
  }

  private static staticInit(): void {
    if (Window.isStaticInitialized) {
      return;
    }
    Window.isStaticInitialized = true;

    ipcMain.on(MessageId.ActivateWindow, (event, windowId: string) => {
      const window = findWindow(windowId);
      window?.activate();
      event.returnValue = !!window;
    });

    ipcMain.on(MessageId.CloseWindow, (event, windowId: string) => {
      const window = findWindow(windowId);
      window?.window?.close();
      event.reply(MessageId.CloseWindowReply, !!window?.window);
    });

    ipcMain.on(MessageId.SetMainWindowTitle, (event, title: string) => {
      const window = findWindow(WindowId.Main);
      window?.window?.setTitle(title);
    });

    ipcMain.on(MessageId.SetWindowTitle, (event, args: WindowTitleArgs) => {
      const window = findWindow(args.windowId);
      window?.window?.setTitle(args.title);
    });
  }

  setCertificateVerifyProc(request: Electron.Request, callback: (verificationResult: number) => void) {
    const { certificate, verificationResult } = request;
    // disable name verification for local.videonext.com certificate
    if (certificate.subjectName === "local.videonext.com" && verificationResult === "net::ERR_CERT_COMMON_NAME_INVALID") {
      callback(SSL_DISABLE_VERIFICATION);
    } else {
      callback(SSL_USE_CHROME_VERIFICATION);
    }
  }

  createMain(): BrowserWindow {
    const { width, height } = this.windowBounds.getSize();

    this.mainWindow = new BrowserWindow({
      width,
      height,
      webPreferences: {
        contextIsolation: false,
        nodeIntegration: true,
        autoplayPolicy: "no-user-gesture-required",
        devTools: true,
        preload: path.join(__dirname, "preload_main.js")
      }
    });

    this.mainWindow.webContents.session.setCertificateVerifyProc(this.setCertificateVerifyProc);

    const server = (new URL(process.env.API_BACKEND!)).host;

    const urlFilter = [`https://${server}/api/call/login`,  `https://${server}/api/call/logout*`];

    this.mainWindow.webContents.session.webRequest.onCompleted({ urls: urlFilter }, (details) => {
      if (details.statusCode === 200 && details.url === urlFilter[0] && details.responseHeaders) {
        details.responseHeaders["set-cookie"].forEach((cookie) => {
          const fragments = cookie.split(";");
          const [key, value] = fragments[0].split("=");
          session.defaultSession.cookies.set({ url: `https://${server}`, name: key, value, path: "/", sameSite: "no_restriction" });
        });
      }
      else {
        details.responseHeaders && details.responseHeaders["set-cookie"].forEach((cookie) => {
          const fragments = cookie.split(";");
          const [key] = fragments[0].split("=");
          session.defaultSession.cookies.remove(urlFilter[1], key);
        });
      }
    });

    this.mainWindow.webContents.on("render-process-gone", (e, details) => {
      console.log("Main: render-process-gone", details);
    });

    this.window = this.mainWindow;
    this.displayId = screen.getPrimaryDisplay().id;

    this.init();

    return this.mainWindow;
  }

  createChild(options: ChildWindowOptions = {}): BrowserWindow {
    this.options = options;
    const { title, isMainParent = true, displayId, rect } = this.options;
    const { width, height } = this.windowBounds.getSize(displayId, rect);

    this.window = new BrowserWindow({
      width,
      height,
      parent: isMainParent ? this.mainWindow : undefined,
      title,
      show: false,
      autoHideMenuBar: true,
      webPreferences: {
        contextIsolation: false,
        nodeIntegration: true,
        autoplayPolicy: "no-user-gesture-required",
        devTools: true,
        preload: path.join(__dirname, "preload.js")
      }
    });

    this.window.webContents.session.setCertificateVerifyProc(this.setCertificateVerifyProc);

    this.window.webContents.on("render-process-gone", (e, details) => {
      console.log("Child: render-process-gone", details);
    });

    this.window.webContents.on("plugin-crashed", (e, details) => {
      console.log("Child: plugin-crashed", details);
    });

    this.rect = rect;
    this.displayId = getDisplay(displayId, rect).id;

    this.init();

    return this.window;
  }

  loadURL(hash?: string): void {
    const window = this.window;
    if (!window) {
      return;
    }

    if (window !== this.mainWindow) {
      window.once("ready-to-show", () => window.show());
    }

    window.webContents.on("did-finish-load", () => {
      window.webContents.send(MessageId.SendWindowId, this.windowId);
      if (window !== this.mainWindow) {
        storeToView(this);
      }
    });

    window.loadURL(
      url.format({
        pathname: path.join(getPackageResourcesPath(), "renderer", "index.html"),
        protocol: "file:",
        slashes: true,
        hash
      })
    );
  }

  getDisplayId(): number {
    return this.displayId ?? getDisplay(this.displayId, this.rect).id;
  }

  protected init(): void {
    const window = this.window;
    if (!window) {
      return;
    }

    this.windowBounds.init(window);

    const { x, y } = this.windowBounds.getPosition(this.options.displayId ?? this.displayId, this.rect);
    window.once("show", () => {
      this.moveToDisplay(x, y);
    });

    // Emitted when the window is closed.
    window.on("closed", () => {
      this.windowBounds.notifyClosed();

      // Dereference the window object, usually you would store windows
      // in an array if your app supports multi windows, this is the time
      // when you should delete the corresponding element.
      const index = Window.windows.indexOf(this);
      if (index >= 0) {
        Window.windows.splice(index, 1);
      }

      if (this.window !== this.mainWindow && this.mainWindow?.webContents) {
        try {
          const args: WindowDisplayChangedArgs = {windowId: this.windowId, displayId: this.getDisplayId()};
          this.mainWindow.webContents.send(MessageId.WindowDisplayChanged, args);
        }
        catch ({message}) {
          console.error("window on closed", message);
        }
      }

      this.window = undefined;
      this.mainWindow = undefined;
    });
  }

  activate(): void {
    if (!this.window) {
      return;
    }
    if (this.window.isMinimized()) {
      this.window.restore();
    }
    this.window.moveTop();
  }

  changeDisplay(displayId?: number): void {
    this.options.displayId = displayId;
    this.displayId = getDisplay(displayId).id;
    const { x, y } = this.windowBounds.getPosition(this.options.displayId ?? this.displayId, this.rect);
    this.activate();
    this.moveToDisplay(x, y);
  }

  protected moveToDisplay(x: number, y: number): void {
    const window = this.window;
    if (!window) {
      return;
    }
    window.setPosition(x, y);
    if (this.options.maximizeOnExternalDisplay && this.displayId && this.displayId !== screen.getPrimaryDisplay().id) {
      window.maximize();
    }
    if (this.mainWindow?.webContents) {
      const args: WindowDisplayChangedArgs = { windowId: this.windowId, displayId: this.getDisplayId() };
      this.mainWindow.webContents.send(MessageId.WindowDisplayChanged, args);
    }
  }
}

export function findWindow(windowId: string): Window | undefined {
  return Window.windows.find(w => w.windowId === windowId);
}

export function findMainWindow(): Window | undefined {
  return findWindow(WindowId.Main);
}

export default Window;
