defmodule Mix.Tasks.ImportMibs do
  @shortdoc "Import MIB files from LibreNMS to priv/mibs"

  @moduledoc """
  Imports MIB files from a LibreNMS installation to priv/mibs for inclusion in the Docker image.

  ## Usage

      # Import all MIB files
      mix import_mibs --source-path ~/dev/librenms/mibs

      # Import specific vendors only
      mix import_mibs --source-path ~/dev/librenms/mibs --vendors mikrotik,cisco,ubiquiti

  ## Options

    * `--source-path` - Path to the LibreNMS mibs directory (required)
    * `--vendors` - Comma-separated list of vendor directories to import (default: all)
    * `--clean` - Remove existing MIBs in priv/mibs before importing (default: false)

  ## Notes

    * Copies MIB files to priv/mibs/ which will be included in the Docker image
    * Preserves directory structure from LibreNMS
    * Standard MIB files are copied to priv/mibs/ root
    * Vendor-specific MIBs are copied to priv/mibs/vendor_name/
    * After importing, commit the changes to git so MIBs are included in the container
  """

  use Mix.Task

  require Logger

  def run(args) do
    {opts, _} =
      OptionParser.parse!(args,
        strict: [
          source_path: :string,
          vendors: :string,
          clean: :boolean
        ]
      )

    source_path = opts[:source_path] || raise "Missing --source-path argument"
    vendors = parse_vendors(opts[:vendors])
    clean = opts[:clean] || false

    if !File.exists?(source_path) do
      raise "Source directory not found: #{source_path}"
    end

    dest_path = Path.join(["priv", "mibs"])

    if clean do
      Logger.info("Cleaning priv/mibs...")
      _ = File.rm_rf!(dest_path)
      File.mkdir_p!(dest_path)
    else
      File.mkdir_p!(dest_path)
    end

    case vendors do
      :all ->
        import_all_vendors(source_path, dest_path)

      vendor_list ->
        import_specific_vendors(source_path, dest_path, vendor_list)
    end
  end

  defp parse_vendors(nil), do: :all
  defp parse_vendors(""), do: :all

  defp parse_vendors(vendors_string) do
    vendors_string
    |> String.split(",")
    |> Enum.map(&String.trim/1)
    |> Enum.reject(&(&1 == ""))
  end

  defp import_all_vendors(source_path, dest_path) do
    Logger.info("Discovering entries in #{source_path}...")

    entries =
      source_path
      |> File.ls!()
      |> Enum.reject(fn name ->
        name == "lost+found" or String.starts_with?(name, ".")
      end)
      |> Enum.sort()

    Logger.info("Found #{length(entries)} entries to import")

    # Separate files and directories
    {files, directories} =
      Enum.split_with(entries, fn name ->
        path = Path.join(source_path, name)
        File.regular?(path)
      end)

    # Copy top-level MIB files (standard MIBs)
    Logger.info("Copying #{length(files)} standard MIB files...")

    Enum.each(files, fn file ->
      copy_file(Path.join(source_path, file), Path.join(dest_path, file))
    end)

    # Copy vendor directories
    Logger.info("Copying #{length(directories)} vendor directories...")

    Enum.each(directories, fn vendor ->
      copy_vendor_directory(source_path, dest_path, vendor)
    end)

    Logger.info("Import complete!")
    Logger.info("Remember to commit the changes: git add priv/mibs && git commit")
  end

  defp import_specific_vendors(source_path, dest_path, vendors) do
    Logger.info("Importing #{length(vendors)} specified vendors...")

    Enum.each(vendors, fn vendor ->
      vendor_source = Path.join(source_path, vendor)

      if File.exists?(vendor_source) do
        copy_vendor_directory(source_path, dest_path, vendor)
      else
        Logger.warning("Vendor directory not found: #{vendor}")
      end
    end)

    Logger.info("Import complete!")
    Logger.info("Remember to commit the changes: git add priv/mibs && git commit")
  end

  defp copy_vendor_directory(source_path, dest_path, vendor) do
    vendor_source = Path.join(source_path, vendor)
    vendor_dest = Path.join(dest_path, vendor)

    Logger.info("Copying #{vendor}...")

    case File.cp_r(vendor_source, vendor_dest) do
      {:ok, _files} ->
        :ok

      {:error, reason, file} ->
        Logger.error("Failed to copy #{vendor}: #{inspect(reason)} (file: #{file})")
    end
  end

  defp copy_file(source, dest) do
    case File.copy(source, dest) do
      {:ok, _bytes} ->
        :ok

      {:error, reason} ->
        Logger.error("Failed to copy #{Path.basename(source)}: #{inspect(reason)}")
    end
  end
end
