import path from "path";
import fs from "fs";
import BouyomiChan from "bouyomi-chan";
import { readFileSync } from "fs";
import { BrowserWindow, App, app, ipcMain } from "electron";
import express from "express";
import { compose, applyMiddleware, createStore, Action, Store, AnyAction, combineReducers } from "redux";

import buildMenu from "./MenuTemplate";
import MainProcessMiddleware from "@common/Middlewares/MainProcessMiddleware";
import createAppReducer from "@common/AppState/AppStateReducer";
import IPCEvent from "@common/events/IPCEvent";
import { Actions as AppStateAction, ChangeURLAction } from "@common/AppState/Actions/AppStateAction";
import AppState, { ChatInfo } from "@common/AppState/AppState";
import openBrowser from "./NativeBridge/OpenBrowser";
import { resumeData, writeData } from "./SaveData";
import { ChatState, initialState as chatInitialState } from "@common/Chat/ChatState";
import { Actions as ChatStateActions, InitChat } from "@common/Chat/ChatStateActions";
import createChatReducer from "@common/Chat/ChatStateReducer";
import { Server } from "http";

export const isDebug = process.env.NODE_ENV == "development";
export const resoucesBasePath = isDebug ? path.resolve(".", "dist") : path.resolve("resources", "app");
export const videoIdParseRegExp = /https:\/\/studio\.youtube\.com\/video\/(.+)\/livestreaming/;

export const packageJsonPath = isDebug ? path.resolve(".", "package.json") : path.resolve("resources", "app", "package.json");

class MyApp {
  private store: Store<{ app: AppState; chat: ChatState }, AppStateAction | ChatStateActions>;
  public mainWindow?: BrowserWindow;

  public childWindows: Map<string, BrowserWindow> = new Map();

  public readonly version: string;

  private app: App;

  private _channelId = "";

  private server: Server | null = null;

  public get state() {
    return this.store.getState();
  }

  public get videoId() {
    const r = videoIdParseRegExp.exec(this.state.app.nowUrl);
    if (r) {
      return r[1];
    } else {
      return null;
    }
  }

  public get channelId() {
    return this._channelId;
  }

  private serverPort = 25252;

  public get serverRunning() {
    return this.server != null;
  }

  public set serverRunning(value: boolean) {
    if (value && this.server === null) {
      this.runServer();
    } else if (!value && this.server) {
      this.stopServer();
    }
  }

  private readonly bouyomiChanPort = 50001;
  private bouyomiChan: BouyomiChan;

  public get bouyomiChanEnabled() {
    return !!this.store.getState().app.bouyomiChanEnabled;
  }

  private _isAlwaysOnTop = true;

  public set isAlwaysOnTop(value: boolean) {
    if (this.mainWindow) {
      this.mainWindow.setAlwaysOnTop(value);
    }
    this._isAlwaysOnTop = value;
  }

  public get isAlwaysOnTop() {
    return this._isAlwaysOnTop;
  }

  constructor(app: App) {
    this.app = app;
    this.app.on("ready", this.onReady);
    this.app.on("window-all-closed", this.onWindowAllClosed);

    const packageJson = fs.readFileSync(packageJsonPath);
    const packageJsonObject = JSON.parse(packageJson.toString("utf-8"));

    this.version = packageJsonObject.version;

    this.bouyomiChan = new BouyomiChan({
      port: this.bouyomiChanPort,
    });

    const initialState = resumeData();
    this.isAlwaysOnTop = !!initialState.isAlwaysOnTop;
    this.serverRunning = !!initialState.fixedChatUrl;
    const reducer = combineReducers({ app: createAppReducer(initialState), chat: createChatReducer(chatInitialState) });

    const myCreateStore = compose(applyMiddleware(MainProcessMiddleware()))(createStore);

    this.store = myCreateStore(reducer);
  }

  public createWindow(id: string, windowOption?: Electron.BrowserWindowConstructorOptions) {
    const window = new BrowserWindow(windowOption);
    window.setAlwaysOnTop(this.isAlwaysOnTop);
    window.addListener("closed", () => {
      this.childWindows.delete(id);
    });
    this.childWindows.set(id, window);
    return window;
  }

  public dispatch(action: AnyAction) {
    if (this.store == null) {
      return;
    }
    this.store.dispatch(action);
  }

  private onReady = () => {
    const state = this.store.getState();

    console.log({ state });
    const windowOption: Electron.BrowserWindowConstructorOptions = {
      title: "YouTubeLiveApp",
      acceptFirstMouse: true,
      width: 1400,
      height: 900,
      webPreferences: {
        webviewTag: true,
        nodeIntegration: true,
      },
    };

    this.registIPCEventListeners();

    const menus = buildMenu();

    this.mainWindow = this.createWindow("MainWindow", windowOption);

    if (isDebug) {
      this.mainWindow.webContents.openDevTools();
    }

    this.mainWindow.loadURL(this.store.getState().app.nowUrl);
    this.mainWindow.setMenu(menus.mainMenuTemplate);

    const willChangePageHanlder = (_event: Electron.Event, url: string) => {
      if (url === "https://www.youtube.com/") {
        console.log("detect www.youtube.com");
        url = "https://studio.youtube.com/";
      }

      if (url.indexOf("https://accounts.google.com/signin/rejected") >= 0) {
        console.log("detect rejected");
        url = "https://studio.youtube.com/";
      }

      this.switchUserAgent(url);
      console.log({ url, UA: this.mainWindow?.webContents.userAgent });
      this.mainWindow?.webContents.loadURL(url);
    };

    this.mainWindow.webContents.on("new-window", this.webContentsOnNewWindow());
    this.mainWindow.webContents.on("will-redirect", willChangePageHanlder);
    this.mainWindow.webContents.on("will-navigate", willChangePageHanlder);

    const didNavigateHandler = (_: Electron.Event, url: string) => {
      console.log({ "did-navigate": url });
      const videoIdResult = videoIdParseRegExp.exec(url);
      if (videoIdResult) {
        const { chat } = this.store.getState();
        console.log({ chat });
        if (!chat.attached) {
          const videoId = videoIdResult[1];
          this.store.dispatch(InitChat(videoId));
          if (!this.childWindows.has(`chat-${videoId}`)) {
            const window = this.createWindow(`chat-${videoId}`, {
              title: `chat-${videoId}`,
              opacity: isDebug ? 1 : 0,
              skipTaskbar: true,
              webPreferences: {
                nodeIntegration: true,
              },
            });
            window.loadURL(`https://www.youtube.com/live_chat?is_popout=1&v=${videoId}`);
            window.setMenu(null);
            const chatboxJSCode = this.loadJSCode(path.resolve(resoucesBasePath, "scripts", "chatbox.js"));
            window.webContents.executeJavaScript(chatboxJSCode);
            if (isDebug) {
              window.webContents.openDevTools();
            }
          }
        }
      }
      console.log({ url });
      this.dispatch(ChangeURLAction(url));
      this.saveAppData();
    };

    this.mainWindow.webContents.on("did-navigate", didNavigateHandler);
    this.mainWindow.webContents.on("did-navigate-in-page", didNavigateHandler);

    this.mainWindow.on("close", () => {
      this.childWindows.forEach((window, key) => {
        if (key !== "MainWindow" && !window.isDestroyed()) {
          window.close();
        }
      });

      this.saveAppData();
    });
  };

  private onWindowAllClosed = () => {
    this.app.quit();
  };

  private webContentsOnNewWindow = () => {
    return (event: Electron.NewWindowEvent, url: string) => {
      // 開こうとしているURLが外部だったらブラウザで開く
      if (!/^https?:\/\/(studio|www)\.youtube.com/.test(url)) {
        event.preventDefault();
        openBrowser(url);
        return;
      }
    };
  };
  private registIPCEventListeners() {
    ipcMain.on(IPCEvent.InitialState.CHANNEL_NAME_FROM_PRELOAD, (event) => {
      event.sender.send(IPCEvent.InitialState.CHANNEL_NAME_FROM_MAIN, this.store?.getState());
    });
    ipcMain.on(IPCEvent.StateChanged.CHANNEL_NAME_FROM_PRELOAD, (_, action: Action<AppState>) => {
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      this.store?.dispatch(action as any);
    });
    ipcMain.on(IPCEvent.BouyomiChan.SPEAK_BOUYOMICHAN_FROM_PRELOAD, (_, chatInfo: ChatInfo) => {
      this.bouyomiChan.speak(`${chatInfo.author} ${chatInfo.message}`);
    });
  }

  private loadJSCode(path: string) {
    return readFileSync(path).toString("utf-8");
  }

  private saveAppData() {
    const { app: state } = this.store.getState();
    writeData({ ...state, isAlwaysOnTop: this.isAlwaysOnTop, fixedChatUrl: this.serverRunning });
  }

  private switchUserAgent(url: string) {
    if (url.indexOf("https://studio.") === 0 || url.indexOf("https://www.") === 0) {
      this.mainWindow?.webContents.setUserAgent(
        "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.125 Safari/537.36"
      );
    } else {
      this.mainWindow?.webContents.setUserAgent("Chrome");
    }
  }

  private runServer() {
    const expressApp = express();

    expressApp.use((req, res, next) => {
      if (req.hostname !== "localhost") {
        res.status(403);
        res.end();
        return;
      }
      res.header("Origin", `localhost:${this.serverPort}`);
      res.header("Access-Control-Allow-Origin", "*");
      res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
      next();
    });

    expressApp.get("/chat", (req, res) => {
      res.status(302);
      res.header("Location", `https://www.youtube.com/live_chat?is_popout=1&v=${this.videoId}`);
      res.end();
    });

    try {
      this.server = expressApp.listen(this.serverPort);
    } catch (e) {
      console.error(e);
    }
  }

  private stopServer() {
    if (this.server != null) {
      this.server.close();
      this.server = null;
    }
  }
}

export default new MyApp(app);