defmodule Towerops.Monitoring do
  @moduledoc """
  The Monitoring context.
  """

  import Ecto.Query

  alias Ecto.Adapters.SQL
  alias Towerops.Monitoring.Check
  alias Towerops.Repo

  @doc """
  Creates a monitoring check.
  """
  def create_check(attrs) do
    %Check{}
    |> Check.changeset(attrs)
    |> Repo.insert()
  end

  @doc """
  Returns the list of checks for an device.
  """
  def list_devices_checks(device_id, limit \\ 100) do
    Repo.all(from(c in Check, where: c.device_id == ^device_id, order_by: [desc: c.checked_at], limit: ^limit))
  end

  @doc """
  Returns the latest check for an device.
  """
  def get_latest_check(device_id) do
    Repo.one(from(c in Check, where: c.device_id == ^device_id, order_by: [desc: c.checked_at], limit: 1))
  end

  @doc """
  Deletes old monitoring checks older than the given date.
  """
  def delete_old_checks(older_than_date) do
    Repo.delete_all(from(c in Check, where: c.checked_at < ^older_than_date))
  end

  @doc """
  Gets hourly statistics for device over a time range.
  Calculates aggregates on-demand from monitoring_checks.
  """
  def get_hourly_stats(device_id, start_time, end_time) do
    query = """
    SELECT
      date_trunc('hour', checked_at) as bucket,
      COUNT(*) as total_checks,
      COUNT(*) FILTER (WHERE status = 'success') as successful_checks,
      COUNT(*) FILTER (WHERE status IN ('failure', 'timeout')) as failed_checks,
      ROUND(AVG(response_time_ms)::numeric, 2) as avg_response_time_ms,
      MIN(response_time_ms) as min_response_time_ms,
      MAX(response_time_ms) as max_response_time_ms,
      ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
    FROM monitoring_checks
    WHERE device_id = $1::uuid
      AND checked_at >= $2
      AND checked_at <= $3
    GROUP BY bucket
    ORDER BY bucket ASC
    """

    SQL.query!(
      Repo,
      query,
      [Ecto.UUID.dump!(device_id), start_time, end_time]
    )
  end

  @doc """
  Gets daily statistics for device over a time range.
  Calculates aggregates on-demand from monitoring_checks.
  """
  def get_daily_stats(device_id, start_time, end_time) do
    query = """
    SELECT
      date_trunc('day', checked_at) as bucket,
      COUNT(*) as total_checks,
      COUNT(*) FILTER (WHERE status = 'success') as successful_checks,
      COUNT(*) FILTER (WHERE status IN ('failure', 'timeout')) as failed_checks,
      ROUND(AVG(response_time_ms)::numeric, 2) as avg_response_time_ms,
      MIN(response_time_ms) as min_response_time_ms,
      MAX(response_time_ms) as max_response_time_ms,
      ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
    FROM monitoring_checks
    WHERE device_id = $1::uuid
      AND checked_at >= $2
      AND checked_at <= $3
    GROUP BY bucket
    ORDER BY bucket ASC
    """

    SQL.query!(
      Repo,
      query,
      [Ecto.UUID.dump!(device_id), start_time, end_time]
    )
  end

  @doc """
  Gets uptime percentage for device over the last N days.
  Calculates on-demand from monitoring_checks.
  """
  def get_uptime_percentage(device_id, days_ago \\ 30) do
    start_time = DateTime.add(DateTime.utc_now(), -days_ago * 24 * 60 * 60, :second)

    query = """
    SELECT
      ROUND(100.0 * COUNT(*) FILTER (WHERE status = 'success') / NULLIF(COUNT(*), 0), 2) as uptime_percentage
    FROM monitoring_checks
    WHERE device_id = $1::uuid
      AND checked_at >= $2
    """

    case SQL.query!(Repo, query, [Ecto.UUID.dump!(device_id), start_time]) do
      %{rows: [[%Decimal{} = uptime]]} ->
        uptime |> Decimal.to_float() |> Float.round(2)

      _ ->
        nil
    end
  end

  @doc """
  Gets latency data for graphing ping response times.

  Returns successful monitoring checks with response times for the specified time range.
  Failed checks are excluded as they have no latency data.

  ## Options
    * `:since` - Only return checks after this datetime
    * `:limit` - Maximum number of checks to return (default: 2880)

  ## Examples

      iex> get_latency_data(device_id, since: DateTime.utc_now() |> DateTime.add(-24, :hour), limit: 100)
      [%Check{response_time_ms: 50, checked_at: ~U[2025-12-21 12:00:00Z]}, ...]

  """
  def get_latency_data(device_id, opts \\ []) do
    since = Keyword.get(opts, :since, DateTime.add(DateTime.utc_now(), -24, :hour))
    limit = Keyword.get(opts, :limit, 2880)

    Check
    |> where([c], c.device_id == ^device_id)
    |> where([c], c.status == :success)
    |> where([c], c.checked_at >= ^since)
    |> where([c], not is_nil(c.response_time_ms))
    |> order_by([c], desc: c.checked_at)
    |> limit(^limit)
    |> Repo.all()
  end
end
