defmodule Towerops.ConfigChanges do
  @moduledoc """
  Context for managing config change events — linking MikroTik backup diffs
  to time windows for performance correlation analysis.
  """

  import Ecto.Query

  alias Towerops.ConfigChanges.ConfigChangeEvent
  alias Towerops.Devices.MikrotikBackups
  alias Towerops.Devices.MikrotikBackups.Differ
  alias Towerops.Repo

  require Logger

  @mikrotik_sections %{
    "firewall" => ~r/^\/(ip )?firewall/m,
    "interface" => ~r/^\/interface/m,
    "routing" => ~r/^\/(ip |routing )(route|ospf|bgp|rip)/m,
    "wireless" => ~r/^\/interface wireless/m,
    "queue" => ~r/^\/queue/m,
    "nat" => ~r/^\/(ip )?firewall nat/m,
    "address" => ~r/^\/ip address/m,
    "dns" => ~r/^\/ip dns/m,
    "dhcp" => ~r/^\/ip dhcp/m,
    "bridge" => ~r/^\/interface bridge/m,
    "vlan" => ~r/^\/interface vlan/m,
    "snmp" => ~r/^\/snmp/m,
    "user" => ~r/^\/user/m,
    "system" => ~r/^\/system/m,
    "ppp" => ~r/^\/ppp/m
  }

  @doc """
  Records a config change event from two backup records.

  Generates a diff, detects changed sections, and stores the event.
  """
  def record_change_event(device_id, before_backup, after_backup) do
    config_before =
      before_backup.config_compressed
      |> MikrotikBackups.decompress_config()
      |> Differ.mask_sensitive_data()

    config_after =
      after_backup.config_compressed
      |> MikrotikBackups.decompress_config()
      |> Differ.mask_sensitive_data()

    case Differ.generate_unified_diff(config_before, config_after) do
      {:ok, ""} ->
        {:ok, :no_changes}

      {:ok, diff} ->
        stats = Differ.calculate_diff_stats(diff)
        sections = detect_changed_sections(diff)

        # Truncate diff_summary to first 2000 chars for storage
        summary = String.slice(diff, 0, 2000)

        device = Repo.get!(Towerops.Devices.Device, device_id)

        attrs = %{
          device_id: device_id,
          organization_id: device.organization_id,
          backup_before_id: before_backup.id,
          backup_after_id: after_backup.id,
          changed_at: after_backup.backed_up_at,
          diff_summary: summary,
          sections_changed: sections,
          change_size: stats.additions + stats.deletions
        }

        %ConfigChangeEvent{}
        |> ConfigChangeEvent.changeset(attrs)
        |> Repo.insert()

      {:error, reason} ->
        Logger.error("Failed to generate diff for config change event: #{inspect(reason)}")
        {:error, reason}
    end
  end

  @doc """
  Lists config change events for a device, most recent first.
  Supports `:after` and `:before` date range filters and `:limit`.
  """
  def list_device_changes(device_id, opts \\ []) do
    limit = Keyword.get(opts, :limit, 50)

    ConfigChangeEvent
    |> where(device_id: ^device_id)
    |> apply_date_filters(opts)
    |> order_by(desc: :changed_at)
    |> limit(^limit)
    |> Repo.all()
  end

  @doc """
  Lists config change events for an organization, most recent first.
  Supports `:after` and `:before` date range filters, `:limit`, and `:preload`.
  """
  def list_org_changes(organization_id, opts \\ []) do
    limit = Keyword.get(opts, :limit, 50)
    preloads = Keyword.get(opts, :preload, [])

    ConfigChangeEvent
    |> where(organization_id: ^organization_id)
    |> apply_date_filters(opts)
    |> order_by(desc: :changed_at)
    |> limit(^limit)
    |> Repo.all()
    |> Repo.preload(preloads)
  end

  @doc """
  Lists config change events for devices at a specific site, most recent first.
  Supports `:after`, `:before`, `:limit`, and `:preload` options.
  """
  def list_site_changes(site_id, opts \\ []) do
    limit = Keyword.get(opts, :limit, 20)
    preloads = Keyword.get(opts, :preload, [:device])

    device_ids =
      Towerops.Devices.list_site_devices(site_id)
      |> Enum.map(& &1.id)

    if device_ids == [] do
      []
    else
      ConfigChangeEvent
      |> where([e], e.device_id in ^device_ids)
      |> apply_date_filters(opts)
      |> order_by(desc: :changed_at)
      |> limit(^limit)
      |> Repo.all()
      |> Repo.preload(preloads)
    end
  end

  @doc """
  Gets a single config change event by ID.
  """
  def get_change_event!(id), do: Repo.get!(ConfigChangeEvent, id)

  @doc """
  Gets a config change event with preloaded associations.
  """
  def get_change_event_with_preloads!(id) do
    ConfigChangeEvent
    |> Repo.get!(id)
    |> Repo.preload([:device, :backup_before, :backup_after])
  end

  # Detect which MikroTik config sections were modified based on diff content
  defp detect_changed_sections(diff) do
    @mikrotik_sections
    |> Enum.filter(fn {_name, pattern} -> Regex.match?(pattern, diff) end)
    |> Enum.map(fn {name, _} -> name end)
    |> Enum.sort()
  end

  defp apply_date_filters(query, opts) do
    query
    |> maybe_filter_after(Keyword.get(opts, :after))
    |> maybe_filter_before(Keyword.get(opts, :before))
  end

  defp maybe_filter_after(query, nil), do: query
  defp maybe_filter_after(query, dt), do: where(query, [e], e.changed_at >= ^dt)

  defp maybe_filter_before(query, nil), do: query
  defp maybe_filter_before(query, dt), do: where(query, [e], e.changed_at <= ^dt)
end
