defmodule MicrowavepropWeb.Router do
  use MicrowavepropWeb, :router

  import MicrowavepropWeb.UserAuth
  import Oban.Web.Router
  import Phoenix.LiveDashboard.Router

  pipeline :browser do
    plug :serve_markdown_if_requested
    plug :accepts, ["html"]
    plug :fetch_session
    plug :store_remote_ip
    plug :store_cf_geo
    plug :fetch_live_flash
    plug :put_root_layout, html: {MicrowavepropWeb.Layouts, :root}
    plug :protect_from_forgery
    plug :put_secure_browser_headers
    plug :put_agent_link_headers
    plug :fetch_current_scope_for_user
  end

  # RFC 8288 Link headers advertising resources for agent/crawler discovery.
  # `/algo` is the human-readable algorithm/service documentation, rendered
  # from `algo.md`. `/sitemap.xml` is the registered "sitemap" rel. About
  # and privacy use their IANA-registered rels.
  @agent_link_header Enum.join(
                       [
                         ~s(</algo>; rel="service-doc"),
                         ~s(</about>; rel="about"),
                         ~s(</privacy>; rel="privacy-policy"),
                         ~s(</sitemap.xml>; rel="sitemap")
                       ],
                       ", "
                     )

  defp put_agent_link_headers(conn, _opts) do
    Plug.Conn.put_resp_header(conn, "link", @agent_link_header)
  end

  # Markdown for Agents: if the client prefers `text/markdown`, serve real
  # markdown for the paths where we have markdown source. Other paths fall
  # through to the `:accepts` plug and 406 — we don't synthesize markdown
  # from LiveView HTML on the fly, since a lossy conversion would mislead
  # agents about site content.
  @external_resource "algo.md"
  @algo_markdown File.read!("algo.md")

  @homepage_markdown """
  # NTMS Propagation Prediction

  The North Texas Microwave Society's propagation prediction service for
  amateur radio bands 10–241 GHz. Scores propagation conditions across
  CONUS using HRRR numerical weather prediction, sounding data, ITU-R
  atmospheric models, and calibration against 57,000+ recorded QSOs.

  ## Primary Resources

  - [Propagation map](/map) — real-time CONUS propagation scores + 18-hour forecast.
  - [Algorithm documentation](/algo) — full methodology, factor weights, ITU-R references (also available as `text/markdown`).
  - [Contact database](/contacts) — 58,000+ historical QSOs used for calibration.
  - [Contact map](/contacts/map) — geographic view of all recorded contacts.
  - [Path calculator](/path) — point-to-point propagation prediction.
  - [Beacons](/beacons) — microwave beacon directory.
  - [Submit a contact](/submit) — add a QSO to the calibration dataset.

  ## Meta

  - [About](/about)
  - [Privacy](/privacy)
  - [Sitemap](/sitemap.xml)

  ## API

  - `GET /api/contacts/map` — JSON tuples of every contact on the map (lat/lon/band/callsigns/mode/distance/timestamp/id). Pre-gzipped, ~5 MB decompressed.
  """

  defp serve_markdown_if_requested(conn, _opts) do
    if wants_markdown?(conn) do
      case markdown_for_path(conn.request_path) do
        nil ->
          conn

        body ->
          conn
          |> Plug.Conn.put_resp_content_type("text/markdown", "utf-8")
          |> Plug.Conn.put_resp_header("x-markdown-tokens", estimate_tokens(body))
          |> Plug.Conn.send_resp(200, body)
          |> Plug.Conn.halt()
      end
    else
      conn
    end
  end

  defp wants_markdown?(conn) do
    conn
    |> Plug.Conn.get_req_header("accept")
    |> Enum.any?(&String.contains?(&1, "text/markdown"))
  end

  defp markdown_for_path("/"), do: @homepage_markdown
  defp markdown_for_path("/algo"), do: @algo_markdown
  defp markdown_for_path(_), do: nil

  # Rough token estimate: ~4 chars per token is the widely-used OpenAI
  # heuristic. Good enough for the `x-markdown-tokens` hint.
  defp estimate_tokens(body), do: body |> byte_size() |> div(4) |> Integer.to_string()

  defp store_remote_ip(conn, _opts) do
    ip_str = conn.remote_ip |> :inet.ntoa() |> to_string()
    Plug.Conn.put_session(conn, :remote_ip, ip_str)
  end

  # Extract Cloudflare visitor geolocation headers (set by a Cloudflare
  # Managed Transform) and stash them in the session so LiveViews can use
  # them to center maps on the visitor's approximate location.
  defp store_cf_geo(conn, _opts) do
    lat = conn |> Plug.Conn.get_req_header("cf-iplatitude") |> List.first()
    lon = conn |> Plug.Conn.get_req_header("cf-iplongitude") |> List.first()

    with {lat_f, ""} when is_float(lat_f) <- lat && Float.parse(lat),
         {lon_f, ""} when is_float(lon_f) <- lon && Float.parse(lon) do
      conn
      |> Plug.Conn.put_session(:cf_lat, lat_f)
      |> Plug.Conn.put_session(:cf_lon, lon_f)
    else
      _ -> conn
    end
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  # Health check — no pipeline, minimal overhead
  get "/health", MicrowavepropWeb.HealthController, :check, log: false

  # Prometheus scrape endpoint. No pipeline so there's no session /
  # CSRF overhead — the intent is a dedicated external Prometheus
  # server polling every 15-30s. If `PROMETHEUS_AUTH_TOKEN` is set
  # (runtime.exs), the endpoint requires a matching bearer token;
  # otherwise it's unauthenticated and expected to be restricted at
  # the ingress / Cloudflare Access layer.
  forward "/metrics", MicrowavepropWeb.MetricsPlug

  # API discovery (RFC 9727 / RFC 9264). These are JSON-only, no pipeline,
  # no session — agents and crawlers fetch them without auth.
  get "/.well-known/api-catalog", MicrowavepropWeb.ApiCatalogController, :catalog
  get "/openapi.json", MicrowavepropWeb.ApiCatalogController, :openapi

  # Agent Skills Discovery (v0.2.0). Index + individual skill documents.
  get "/.well-known/agent-skills/index.json", MicrowavepropWeb.AgentSkillsController, :index

  get "/.well-known/agent-skills/:name/SKILL.md",
      MicrowavepropWeb.AgentSkillsController,
      :skill

  # Admin-only routes must come first so that literal segments like
  # `/beacons/:id/edit` are registered before the public `/beacons/:id`.
  scope "/", MicrowavepropWeb do
    pipe_through [:browser, :require_authenticated_user]

    live_session :admin, on_mount: [{MicrowavepropWeb.UserAuth, :require_admin}] do
      live "/beacons/:id/edit", BeaconLive.Form, :edit

      live "/status", StatusLive
      live "/admin/contact-edits", Admin.ContactEditLive

      live "/users", UserManagementLive.Index, :index
      live "/users/:id/edit", UserManagementLive.Edit, :edit
    end
  end

  scope "/", MicrowavepropWeb do
    pipe_through :browser

    get "/", PageController, :home
    get "/api/contacts/map", ContactMapController, :show

    live_session :public, on_mount: [{MicrowavepropWeb.UserAuth, :default}] do
      live "/submit", SubmitLive
      live "/imports/:id", ImportLive
      live "/map", MapLive
      live "/weather", WeatherMapLive
      live "/contacts", ContactLive.Index
      live "/contacts/map", ContactMapLive
      live "/contacts/:id", ContactLive.Show
      live "/path", PathLive
      live "/rover", RoverLive
      live "/algo", AlgoLive
      live "/about", AboutLive
      live "/privacy", PrivacyLive

      # Beacons: public read + submit (pending admin approval)
      live "/beacons", BeaconLive.Index, :index
      live "/beacons/new", BeaconLive.Form, :new
      live "/beacons/:id", BeaconLive.Show, :show

      # Public per-user profile (contributions: contacts + beacons submitted)
      live "/u/:callsign", UserProfileLive
    end

    # Redirect old /qsos routes
    get "/qsos", PageController, :redirect_contacts
    get "/qsos/:id", PageController, :redirect_contact
  end

  # Other scopes may use custom stacks.
  # scope "/api", MicrowavepropWeb do
  #   pipe_through :api
  # end

  scope "/" do
    pipe_through [:browser, :require_authenticated_user]

    live_dashboard "/admin/dashboard",
      metrics: MicrowavepropWeb.Telemetry,
      ecto_repos: [Microwaveprop.Repo],
      ecto_psql_extras_options: [long_running_queries: [threshold: "200 milliseconds"]],
      on_mount: [{MicrowavepropWeb.UserAuth, :require_admin}]

    oban_dashboard("/admin/oban", on_mount: [{MicrowavepropWeb.UserAuth, :require_admin}])
  end

  # Enable Swoosh mailbox preview in development
  if Application.compile_env(:microwaveprop, :dev_routes) do
    scope "/dev" do
      pipe_through :browser

      forward "/mailbox", Plug.Swoosh.MailboxPreview
    end
  end

  ## Authentication routes

  scope "/", MicrowavepropWeb do
    pipe_through [:browser, :redirect_if_user_is_authenticated]

    get "/users/register", UserRegistrationController, :new
    post "/users/register", UserRegistrationController, :create

    get "/users/reset-password", UserResetPasswordController, :new
    post "/users/reset-password", UserResetPasswordController, :create
    get "/users/reset-password/:token", UserResetPasswordController, :edit
    put "/users/reset-password/:token", UserResetPasswordController, :update
  end

  scope "/", MicrowavepropWeb do
    pipe_through [:browser, :require_authenticated_user]

    get "/users/settings", UserSettingsController, :edit
    put "/users/settings", UserSettingsController, :update
    get "/users/settings/confirm-email/:token", UserSettingsController, :confirm_email

    post "/users/beacon-monitors", BeaconMonitorController, :create
    delete "/users/beacon-monitors/:id", BeaconMonitorController, :delete
  end

  scope "/", MicrowavepropWeb do
    pipe_through [:browser]

    get "/users/log-in", UserSessionController, :new
    get "/users/confirm/:token", UserSessionController, :confirm
    post "/users/log-in", UserSessionController, :create
    delete "/users/log-out", UserSessionController, :delete
  end
end
