defmodule Towerops.Workers.MikrotikBackupWorker do
  @moduledoc """
  Oban worker that triggers daily MikroTik configuration backups.

  Runs daily at 3 AM via cron schedule. For each eligible device, sends a backup
  job to the assigned agent and creates a tracking request.

  Eligible devices:
  - Have MikroTik detected (manufacturer contains "MikroTik" or "RouterOS")
  - Have MikroTik API enabled
  - Have credentials configured (username present)
  - Are assigned to an agent
  """

  use Oban.Worker, queue: :maintenance, max_attempts: 3

  alias Towerops.Agent.AgentJob
  alias Towerops.Agent.MikrotikCommand
  alias Towerops.Agent.MikrotikDevice
  alias Towerops.Devices
  alias Towerops.Devices.BackupRequests

  require Logger

  @impl Oban.Worker
  @spec perform(Oban.Job.t()) :: :ok | {:error, String.t()}
  def perform(%Oban.Job{}) do
    Logger.info("Starting MikroTik configuration backup job")

    devices = list_backup_eligible_devices()
    Logger.info("Found #{length(devices)} MikroTik devices to backup")

    results = Enum.map(devices, &backup_device/1)

    success_count = Enum.count(results, &match?({:ok, _}, &1))
    error_count = Enum.count(results, &match?({:error, _}, &1))
    skipped_count = Enum.count(results, &match?({:skipped, _}, &1))

    Logger.info("Backup job complete",
      success: success_count,
      errors: error_count,
      skipped: skipped_count
    )

    if error_count > 0, do: {:error, "#{error_count} backups failed"}, else: :ok
  end

  defp list_backup_eligible_devices do
    # Query all devices with:
    # - MikroTik detected (manufacturer contains "MikroTik" or "RouterOS")
    # - MikroTik API enabled
    # - Has credentials (username present)
    # - Assigned to an agent
    Devices.list_mikrotik_devices_with_api()
  end

  defp backup_device(device) do
    # Resolve which agent this device should use (hierarchical: device -> site -> org -> global)
    agent_token_id = Devices.resolve_agent_token_id(device)

    if is_nil(agent_token_id) do
      Logger.warning("Device has no agent assigned, skipping backup", device_id: device.id)
      {:skipped, :no_agent}
    else
      # Build backup job
      job_id = "backup:#{device.id}:#{DateTime.to_unix(DateTime.utc_now())}"

      mikrotik_config = Devices.get_mikrotik_config(device)

      # Generate unique filename for this backup (without .rsc extension, RouterOS adds it)
      backup_filename = "towerops-backup-#{DateTime.to_unix(DateTime.utc_now())}"

      # Build chunked read commands
      # Max chunk size is 32KB (32768 bytes), we'll read 10 chunks to cover 320KB
      # Most MikroTik configs are under this size
      chunk_size = 32_768
      num_chunks = 10

      read_commands =
        for i <- 0..(num_chunks - 1) do
          %MikrotikCommand{
            command: "/file/read",
            args: %{
              "file" => "#{backup_filename}.rsc",
              "offset" => to_string(i * chunk_size),
              "chunk-size" => to_string(chunk_size)
            }
          }
        end

      job = %AgentJob{
        job_id: job_id,
        job_type: :MIKROTIK,
        device_id: device.id,
        mikrotik_device: %MikrotikDevice{
          ip: device.ip_address,
          username: mikrotik_config.username,
          password: mikrotik_config.password,
          port: mikrotik_config.port,
          ssh_port: mikrotik_config.ssh_port,
          use_ssl: mikrotik_config.use_ssl
        },
        mikrotik_commands:
          [
            # Step 1: Export config to file on router (compact format, no defaults)
            %MikrotikCommand{
              command: "/export",
              args: %{"file" => backup_filename, "compact" => ""}
            }
          ] ++
            read_commands ++
            [
              # Final step: Delete the temporary file
              %MikrotikCommand{
                command: "/file/remove",
                args: %{"numbers" => "#{backup_filename}.rsc"}
              }
            ]
      }

      # Create tracking request
      case BackupRequests.create_request(device.id, job_id) do
        {:ok, _request} ->
          # Broadcast to agent via PubSub
          Phoenix.PubSub.broadcast(
            Towerops.PubSub,
            "agent:#{agent_token_id}:backup",
            {:backup_requested, job}
          )

          Logger.info("Backup job sent to agent",
            device_id: device.id,
            job_id: job_id,
            agent_token_id: agent_token_id
          )

          {:ok, job_id}

        {:error, reason} ->
          Logger.error("Failed to create backup request",
            device_id: device.id,
            reason: inspect(reason)
          )

          {:error, reason}
      end
    end
  end
end
