defmodule Mix.Tasks.UploadLibrenms do
  @shortdoc "Upload MIBs and import profiles from LibreNMS installation"

  @moduledoc """
  Uploads MIB files and imports device profiles from a LibreNMS installation.

  ## Usage

      # Upload from local LibreNMS installation
      mix upload_librenms --librenms-path ~/dev/librenms

      # Upload to specific server
      mix upload_librenms --librenms-path ~/dev/librenms --url https://towerops.net

      # Upload specific profiles only
      mix upload_librenms --librenms-path ~/dev/librenms --profiles mikrotik,cisco

  ## Options

    * `--librenms-path` - Path to LibreNMS installation directory (required)
    * `--token` - Superuser API token for authentication (required for MIB upload)
    * `--url` - Server URL (default: http://localhost:4000)
    * `--profiles` - Comma-separated list of profiles to import (default: all)

  ## What it does

  1. Uploads all MIB files from `{librenms-path}/mibs` to the server
  2. Imports device profiles from `{librenms-path}/resources/definitions`

  ## Requirements

    * LibreNMS installation with mibs/ and resources/definitions/ directories
    * Server must be running and accessible
  """

  use Mix.Task

  require Logger

  @requirements ["app.start"]

  def run(args) do
    {opts, _} =
      OptionParser.parse!(args,
        strict: [
          librenms_path: :string,
          url: :string,
          token: :string,
          profiles: :string
        ]
      )

    librenms_path = opts[:librenms_path] || raise "Missing --librenms-path argument"
    base_url = opts[:url] || "http://localhost:4000"
    token = opts[:token]
    profiles = opts[:profiles]

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

    # Step 1: Upload MIBs
    mibs_path = Path.join(librenms_path, "mibs")

    if File.exists?(mibs_path) do
      if token do
        Logger.info("Step 1/2: Uploading MIB files from #{mibs_path}...")
        upload_mibs(mibs_path, base_url, token)
      else
        Logger.warning("Skipping MIB upload: --token is required for MIB uploads (superuser API token needed)")
      end
    else
      Logger.warning("MIBs directory not found: #{mibs_path}, skipping MIB upload")
    end

    # Step 2: Import profiles
    definitions_path = Path.join(librenms_path, "resources/definitions")

    if File.exists?(definitions_path) do
      Logger.info("Step 2/2: Importing device profiles from #{definitions_path}...")
      import_profiles(definitions_path, profiles)
    else
      Logger.warning("Definitions directory not found: #{definitions_path}, skipping profile import")
    end

    Logger.info("Upload complete!")
  end

  defp upload_mibs(mibs_path, base_url, token) do
    # Find all vendor directories in mibs/
    vendor_dirs =
      mibs_path
      |> Path.join("*")
      |> Path.wildcard()
      |> Enum.filter(&File.dir?/1)
      |> Enum.sort()

    if Enum.empty?(vendor_dirs) do
      Logger.warning("No vendor directories found in #{mibs_path}")
    else
      Logger.info("Found #{length(vendor_dirs)} vendor directories")

      # Upload each vendor's MIBs
      results = Enum.flat_map(vendor_dirs, &upload_vendor_mibs(&1, base_url, token))

      report_upload_results(results)
    end
  end

  defp upload_vendor_mibs(vendor_dir, base_url, token) do
    vendor = Path.basename(vendor_dir)
    Logger.info("Uploading MIBs for vendor: #{vendor}")

    vendor_dir
    |> find_mib_files()
    |> Enum.map(&upload_single_mib(&1, vendor, base_url, token))
    |> Enum.reject(&is_nil/1)
  end

  defp find_mib_files(vendor_dir) do
    vendor_dir
    |> Path.join("*")
    |> Path.wildcard()
    |> Enum.filter(&File.regular?/1)
    |> Enum.sort()
  end

  defp upload_single_mib(file_path, vendor, base_url, token) do
    if File.regular?(file_path) do
      filename = Path.basename(file_path)
      Logger.debug("  Uploading: #{filename}")

      case upload_mib_file(file_path, vendor, base_url, token) do
        :ok ->
          {:ok, filename}

        {:error, reason} ->
          Logger.error("  ✗ Failed to upload #{filename}: #{inspect(reason)}")
          {:error, filename, reason}
      end
    else
      Logger.debug("  Skipping non-file: #{Path.basename(file_path)}")
      nil
    end
  end

  defp report_upload_results(results) do
    successes = Enum.count(results, &match?({:ok, _}, &1))
    failures = Enum.count(results, &match?({:error, _, _}, &1))
    Logger.info("MIB upload complete: #{successes} succeeded, #{failures} failed")
  end

  defp upload_mib_file(file_path, vendor, base_url, token) do
    url = "#{base_url}/api/v1/mibs"
    filename = Path.basename(file_path)

    # Build base request
    req = Req.new(url: url)

    # Add authorization header if token is provided
    req =
      if token do
        Req.Request.put_header(req, "authorization", "Bearer #{token}")
      else
        req
      end

    # Add multipart form data
    form_data = [
      file: {file_path, filename: filename},
      vendor: vendor
    ]

    case Req.post(req, form_multipart: form_data) do
      {:ok, %{status: status}} when status in 200..299 ->
        :ok

      {:ok, %{status: status, body: body}} ->
        {:error, "HTTP #{status}: #{inspect(body)}"}

      {:error, reason} ->
        {:error, reason}
    end
  end

  defp import_profiles(definitions_path, profiles_filter) do
    # Call the existing import_profiles task
    args =
      case profiles_filter do
        nil ->
          ["--source-path", definitions_path]

        profiles ->
          ["--source-path", definitions_path, "--profiles", profiles]
      end

    Mix.Task.run("import_profiles", args)
  end
end
