import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import { resolvePackagedFirstRunPluginAssets } from './first-run-setup-plugin';

export interface ManagedLinuxRuntimePluginPaths {
  dataDir: string;
  rootDir: string;
  pluginDir: string;
  pluginEntrypointPath: string;
  pluginConfigPath: string;
  themePath: string;
  thumbnailerPath: string;
}

export interface EnsureLinuxRuntimePluginAssetsResult {
  ok: boolean;
  status: 'installed' | 'already-present' | 'failed';
  path?: string;
  error?: string;
}

interface RuntimePluginAssetSources {
  pluginDirSource?: string;
  pluginConfigSource?: string;
  themeSourcePath?: string;
  thumbnailerSourcePath?: string;
}

interface RuntimePluginDirentLike {
  name: string;
  isDirectory(): boolean;
}

interface EnsureLinuxRuntimePluginAssetsOptions {
  platform?: NodeJS.Platform;
  homeDir?: string;
  xdgDataHome?: string;
  pathModule?: typeof path;
  existsSync?: (candidate: string) => boolean;
  resolveBundledAssets?: () => RuntimePluginAssetSources | null;
  mkdir?: (targetPath: string, options: { recursive: true }) => Promise<void>;
  readdir?: (
    targetPath: string,
    options: { withFileTypes: true },
  ) => Promise<RuntimePluginDirentLike[]>;
  copyFile?: (sourcePath: string, targetPath: string) => Promise<void>;
  rename?: (fromPath: string, toPath: string) => Promise<void>;
  rm?: (targetPath: string, options: { recursive?: boolean; force?: boolean }) => Promise<void>;
}

function errorMessage(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

export function resolveManagedLinuxRuntimePluginPaths(options: {
  homeDir?: string;
  xdgDataHome?: string;
  pathModule?: typeof path;
}): ManagedLinuxRuntimePluginPaths {
  const pathModule = options.pathModule ?? path;
  const homeDir = options.homeDir ?? os.homedir();
  const explicitXdgDataHome = options.xdgDataHome?.trim();
  const envXdgDataHome = process.env.XDG_DATA_HOME?.trim();
  const xdgDataHome =
    explicitXdgDataHome || envXdgDataHome || pathModule.join(homeDir, '.local', 'share');
  const dataDir = pathModule.join(xdgDataHome, 'SubMiner');
  const rootDir = pathModule.join(dataDir, 'plugin');
  const pluginDir = pathModule.join(rootDir, 'subminer');
  return {
    dataDir,
    rootDir,
    pluginDir,
    pluginEntrypointPath: pathModule.join(pluginDir, 'main.lua'),
    pluginConfigPath: pathModule.join(rootDir, 'subminer.conf'),
    themePath: pathModule.join(dataDir, 'themes', 'subminer.rasi'),
    thumbnailerPath: pathModule.join(
      dataDir,
      'thumbnailers',
      'subminer-ffmpegthumbnailer.thumbnailer',
    ),
  };
}

async function copyDirectoryRecursive(
  sourceDir: string,
  targetDir: string,
  options: Required<
    Pick<EnsureLinuxRuntimePluginAssetsOptions, 'mkdir' | 'readdir' | 'copyFile' | 'pathModule'>
  >,
): Promise<void> {
  await options.mkdir(targetDir, { recursive: true });
  const entries = await options.readdir(sourceDir, { withFileTypes: true });
  for (const entry of entries) {
    const sourcePath = options.pathModule.join(sourceDir, entry.name);
    const targetPath = options.pathModule.join(targetDir, entry.name);
    if (entry.isDirectory()) {
      await copyDirectoryRecursive(sourcePath, targetPath, options);
      continue;
    }
    await options.copyFile(sourcePath, targetPath);
  }
}

function resolveBundledAssetPath(options: {
  dirname: string;
  appPath: string;
  resourcesPath: string;
  existsSync: (candidate: string) => boolean;
  relativePath: string;
}): string | null {
  const roots = [
    path.join(options.resourcesPath, 'assets'),
    path.join(options.resourcesPath, 'app.asar', 'assets'),
    path.join(options.appPath, 'assets'),
    path.join(options.dirname, '..', 'assets'),
    path.join(options.dirname, '..', '..', 'assets'),
    path.join(options.dirname, '..', '..', '..', 'assets'),
  ];

  for (const root of roots) {
    const candidate = path.join(root, options.relativePath);
    if (options.existsSync(candidate)) return candidate;
  }

  return null;
}

function resolveBundledAssetsDefault(
  existsSync: (candidate: string) => boolean,
): RuntimePluginAssetSources {
  const resourcesPath = process.resourcesPath ?? path.dirname(process.execPath);
  const pluginAssets = resolvePackagedFirstRunPluginAssets({
    dirname: __dirname,
    appPath: process.execPath,
    resourcesPath,
    existsSync,
  });

  const themeSourcePath = resolveBundledAssetPath({
    dirname: __dirname,
    appPath: process.execPath,
    resourcesPath,
    existsSync,
    relativePath: path.join('themes', 'subminer.rasi'),
  });
  const thumbnailerSourcePath = resolveBundledAssetPath({
    dirname: __dirname,
    appPath: process.execPath,
    resourcesPath,
    existsSync,
    relativePath: path.join('thumbnailers', 'subminer-ffmpegthumbnailer.thumbnailer'),
  });

  return {
    ...(pluginAssets ?? {}),
    ...(themeSourcePath ? { themeSourcePath } : {}),
    ...(thumbnailerSourcePath ? { thumbnailerSourcePath } : {}),
  };
}

export async function ensureLinuxRuntimePluginAssets(
  options: EnsureLinuxRuntimePluginAssetsOptions = {},
): Promise<EnsureLinuxRuntimePluginAssetsResult> {
  const platform = options.platform ?? process.platform;
  if (platform !== 'linux') {
    return {
      ok: false,
      status: 'failed',
      error: 'Linux runtime plugin asset install is only supported on Linux.',
    };
  }

  const pathModule = options.pathModule ?? path;
  const existsSync = options.existsSync ?? fs.existsSync;
  const mkdir =
    options.mkdir ??
    (async (targetPath, mkdirOptions) => {
      await fs.promises.mkdir(targetPath, mkdirOptions);
    });
  const readdir =
    options.readdir ??
    ((targetPath, readdirOptions) =>
      fs.promises.readdir(targetPath, readdirOptions) as Promise<RuntimePluginDirentLike[]>);
  const copyFile =
    options.copyFile ?? ((sourcePath, targetPath) => fs.promises.copyFile(sourcePath, targetPath));
  const rename = options.rename ?? ((fromPath, toPath) => fs.promises.rename(fromPath, toPath));
  const rm = options.rm ?? ((targetPath, rmOptions) => fs.promises.rm(targetPath, rmOptions));

  const managedPaths = resolveManagedLinuxRuntimePluginPaths({
    homeDir: options.homeDir,
    xdgDataHome: options.xdgDataHome,
    pathModule,
  });
  const pluginAssetsExist =
    existsSync(managedPaths.pluginEntrypointPath) && existsSync(managedPaths.pluginConfigPath);
  const themeExists = existsSync(managedPaths.themePath);
  const thumbnailerExists = existsSync(managedPaths.thumbnailerPath);
  if (pluginAssetsExist && themeExists && thumbnailerExists) {
    return {
      ok: true,
      status: 'already-present',
      path: managedPaths.pluginEntrypointPath,
    };
  }

  const bundledAssets =
    (options.resolveBundledAssets
      ? options.resolveBundledAssets()
      : resolveBundledAssetsDefault(existsSync)) ?? {};

  const shouldInstallPluginAssets = !pluginAssetsExist;
  const shouldInstallTheme = !themeExists;
  const shouldInstallThumbnailer = !thumbnailerExists;
  if (
    shouldInstallPluginAssets &&
    (!bundledAssets.pluginDirSource || !bundledAssets.pluginConfigSource)
  ) {
    return {
      ok: false,
      status: 'failed',
      error: 'Bundled Linux runtime plugin assets were not found.',
    };
  }
  if (shouldInstallTheme && !bundledAssets.themeSourcePath) {
    return {
      ok: false,
      status: 'failed',
      error: 'Bundled Linux runtime theme asset was not found.',
    };
  }
  if (shouldInstallThumbnailer && !bundledAssets.thumbnailerSourcePath) {
    return {
      ok: false,
      status: 'failed',
      error: 'Bundled Linux rofi thumbnailer asset was not found.',
    };
  }

  const stagingSuffix = `${process.pid}-${Date.now()}`;
  const stagedPluginDir = pathModule.join(managedPaths.rootDir, `.subminer-stage-${stagingSuffix}`);
  const stagedPluginConfigPath = pathModule.join(
    managedPaths.rootDir,
    `.subminer.conf-stage-${stagingSuffix}`,
  );
  const stagedThemePath = pathModule.join(
    pathModule.dirname(managedPaths.themePath),
    `.subminer.rasi-stage-${stagingSuffix}`,
  );
  const stagedThumbnailerPath = pathModule.join(
    pathModule.dirname(managedPaths.thumbnailerPath),
    `.subminer-ffmpegthumbnailer.thumbnailer-stage-${stagingSuffix}`,
  );
  let pluginDirInstalled = false;
  let pluginConfigInstalled = false;
  let themeInstalled = false;
  let thumbnailerInstalled = false;

  try {
    if (shouldInstallPluginAssets) {
      const pluginDirSource = bundledAssets.pluginDirSource;
      const pluginConfigSource = bundledAssets.pluginConfigSource;
      if (!pluginDirSource || !pluginConfigSource) {
        throw new Error('Bundled Linux runtime plugin assets were not found.');
      }
      await mkdir(managedPaths.rootDir, { recursive: true });
      await copyDirectoryRecursive(pluginDirSource, stagedPluginDir, {
        mkdir,
        readdir,
        copyFile,
        pathModule,
      });
      await copyFile(pluginConfigSource, stagedPluginConfigPath);
    }
    if (shouldInstallTheme) {
      const themeSourcePath = bundledAssets.themeSourcePath;
      if (!themeSourcePath) {
        throw new Error('Bundled Linux runtime theme asset was not found.');
      }
      await mkdir(pathModule.dirname(managedPaths.themePath), { recursive: true });
      await copyFile(themeSourcePath, stagedThemePath);
    }
    if (shouldInstallThumbnailer) {
      const thumbnailerSourcePath = bundledAssets.thumbnailerSourcePath;
      if (!thumbnailerSourcePath) {
        throw new Error('Bundled Linux rofi thumbnailer asset was not found.');
      }
      await mkdir(pathModule.dirname(managedPaths.thumbnailerPath), { recursive: true });
      await copyFile(thumbnailerSourcePath, stagedThumbnailerPath);
    }
    if (shouldInstallPluginAssets) {
      await rm(managedPaths.pluginDir, { recursive: true, force: true });
      await rm(managedPaths.pluginConfigPath, { force: true });
      await rename(stagedPluginDir, managedPaths.pluginDir);
      pluginDirInstalled = true;
      await rename(stagedPluginConfigPath, managedPaths.pluginConfigPath);
      pluginConfigInstalled = true;
    }
    if (shouldInstallTheme) {
      await rm(managedPaths.themePath, { force: true });
      await rename(stagedThemePath, managedPaths.themePath);
      themeInstalled = true;
    }
    if (shouldInstallThumbnailer) {
      await rm(managedPaths.thumbnailerPath, { force: true });
      await rename(stagedThumbnailerPath, managedPaths.thumbnailerPath);
      thumbnailerInstalled = true;
    }

    return {
      ok: true,
      status: 'installed',
      path: managedPaths.pluginEntrypointPath,
    };
  } catch (error) {
    if (pluginDirInstalled && !pluginConfigInstalled) {
      await rm(managedPaths.pluginDir, { recursive: true, force: true }).catch(() => {});
    }
    if (pluginConfigInstalled && !pluginDirInstalled) {
      await rm(managedPaths.pluginConfigPath, { force: true }).catch(() => {});
    }
    if (themeInstalled) {
      await rm(managedPaths.themePath, { force: true }).catch(() => {});
    }
    if (thumbnailerInstalled) {
      await rm(managedPaths.thumbnailerPath, { force: true }).catch(() => {});
    }
    await rm(stagedPluginDir, { recursive: true, force: true }).catch(() => {});
    await rm(stagedPluginConfigPath, { force: true }).catch(() => {});
    await rm(stagedThemePath, { force: true }).catch(() => {});
    await rm(stagedThumbnailerPath, { force: true }).catch(() => {});
    return {
      ok: false,
      status: 'failed',
      error: errorMessage(error),
    };
  }
}
