defmodule BerrypodWeb.AnalyticsExportController do
  use BerrypodWeb, :controller

  alias Berrypod.Analytics

  @periods %{
    "today" => 0,
    "7d" => 6,
    "30d" => 29,
    "12m" => 364
  }

  @filterable ~w(pathname referrer_source country_code browser os screen_size)

  def export(conn, params) do
    period = if Map.has_key?(@periods, params["period"]), do: params["period"], else: "30d"
    filters = parse_filters(params)
    range = date_range(period)

    trend_data =
      if period == "today",
        do: Analytics.visitors_by_hour(range, filters),
        else: Analytics.visitors_by_date(range, filters)

    visitors = Analytics.count_visitors(range, filters)
    pageviews = Analytics.count_pageviews(range, filters)
    bounce_rate = Analytics.bounce_rate(range, filters)
    avg_duration = Analytics.avg_duration(range, filters)

    top_pages = Analytics.top_pages(range, filters: filters, limit: 10_000)
    entry_pages = Analytics.entry_pages(range, filters: filters, limit: 10_000)
    exit_pages = Analytics.exit_pages(range, filters: filters, limit: 10_000)
    top_sources = Analytics.top_sources(range, filters: filters, limit: 10_000)
    top_referrers = Analytics.top_referrers(range, filters: filters, limit: 10_000)
    countries = Analytics.top_countries(range, filters: filters, limit: 10_000)
    browsers = Analytics.device_breakdown(range, :browser, filters)
    oses = Analytics.device_breakdown(range, :os, filters)
    screen_sizes = Analytics.device_breakdown(range, :screen_size, filters)
    funnel = Analytics.funnel(range, filters)
    revenue = Analytics.total_revenue(range, filters)

    csv_files = [
      {"overview.csv", overview_csv(visitors, pageviews, bounce_rate, avg_duration)},
      {trend_filename(period), trend_csv(trend_data, period)},
      {"pages.csv", pages_csv(top_pages)},
      {"entry_pages.csv", entry_exit_csv(entry_pages)},
      {"exit_pages.csv", entry_exit_csv(exit_pages)},
      {"sources.csv", sources_csv(top_sources)},
      {"referrers.csv", referrers_csv(top_referrers)},
      {"countries.csv", countries_csv(countries)},
      {"browsers.csv", devices_csv(browsers)},
      {"operating_systems.csv", devices_csv(oses)},
      {"screen_sizes.csv", devices_csv(screen_sizes)},
      {"funnel.csv", funnel_csv(funnel, revenue)}
    ]

    zip_entries =
      Enum.map(csv_files, fn {name, content} ->
        {String.to_charlist(name), content}
      end)

    {:ok, {_name, zip_data}} = :zip.create(~c"export.zip", zip_entries, [:memory])

    today = Date.to_iso8601(Date.utc_today())
    filename = "analytics-#{period}-#{today}.zip"

    conn
    |> put_resp_content_type("application/zip")
    |> put_resp_header("content-disposition", ~s(attachment; filename="#{filename}"))
    |> send_resp(200, zip_data)
  end

  # -- CSV builders --

  defp overview_csv(visitors, pageviews, bounce_rate, avg_duration) do
    encode_csv([
      ["metric", "value"],
      ["visitors", visitors],
      ["pageviews", pageviews],
      ["bounce_rate_pct", bounce_rate],
      ["avg_duration_seconds", avg_duration]
    ])
  end

  defp trend_filename("today"), do: "visitors_by_hour.csv"
  defp trend_filename(_), do: "visitors_by_date.csv"

  defp trend_csv(data, "today") do
    rows = [["hour", "visitors"] | Enum.map(data, fn r -> [r.hour, r.visitors] end)]
    encode_csv(rows)
  end

  defp trend_csv(data, _period) do
    rows = [["date", "visitors"] | Enum.map(data, fn r -> [r.date, r.visitors] end)]
    encode_csv(rows)
  end

  defp pages_csv(rows) do
    encode_csv([
      ["pathname", "visitors", "pageviews"]
      | Enum.map(rows, fn r -> [r.pathname, r.visitors, r.pageviews] end)
    ])
  end

  defp entry_exit_csv(rows) do
    encode_csv([
      ["pathname", "sessions"]
      | Enum.map(rows, fn r -> [r.pathname, r.sessions] end)
    ])
  end

  defp sources_csv(rows) do
    encode_csv([
      ["source", "visitors"]
      | Enum.map(rows, fn r -> [r.source, r.visitors] end)
    ])
  end

  defp referrers_csv(rows) do
    encode_csv([
      ["referrer", "visitors"]
      | Enum.map(rows, fn r -> [r.referrer, r.visitors] end)
    ])
  end

  defp countries_csv(rows) do
    encode_csv([
      ["country_code", "visitors"]
      | Enum.map(rows, fn r -> [r.country_code, r.visitors] end)
    ])
  end

  defp devices_csv(rows) do
    encode_csv([
      ["name", "visitors"]
      | Enum.map(rows, fn r -> [r.name, r.visitors] end)
    ])
  end

  defp funnel_csv(funnel, revenue) do
    top = funnel.product_views

    steps = [
      {"product_views", funnel.product_views},
      {"add_to_carts", funnel.add_to_carts},
      {"checkouts", funnel.checkouts},
      {"purchases", funnel.purchases}
    ]

    step_rows =
      Enum.map(steps, fn {name, count} ->
        pct = if top > 0, do: Float.round(count / top * 100, 1), else: 0.0
        [name, count, pct]
      end)

    encode_csv(
      [["step", "count", "conversion_pct"]] ++ step_rows ++ [["revenue_pence", revenue, ""]]
    )
  end

  # -- CSV encoding --

  defp encode_csv(rows) do
    rows
    |> Enum.map(fn row ->
      row
      |> Enum.map(&encode_field/1)
      |> Enum.join(",")
    end)
    |> Enum.join("\r\n")
    |> then(&(&1 <> "\r\n"))
  end

  defp encode_field(nil), do: ""

  defp encode_field(val) when is_binary(val) do
    if String.contains?(val, [",", "\"", "\n", "\r"]) do
      ~s("#{String.replace(val, "\"", "\"\"")}")
    else
      val
    end
  end

  defp encode_field(val), do: to_string(val)

  # -- Helpers --

  defp date_range(period) do
    days = Map.fetch!(@periods, period)
    today = Date.utc_today()
    start_date = Date.add(today, -days)
    end_date = Date.add(today, 1)

    {DateTime.new!(start_date, ~T[00:00:00], "Etc/UTC"),
     DateTime.new!(end_date, ~T[00:00:00], "Etc/UTC")}
  end

  defp parse_filters(params) do
    filter_params = Map.get(params, "filter", %{})

    Enum.reduce(filter_params, %{}, fn {key, val}, acc ->
      if key in @filterable do
        Map.put(acc, String.to_existing_atom(key), val)
      else
        acc
      end
    end)
  end
end
