defmodule MvWeb.MemberLive.Show do
  @moduledoc """
  LiveView for displaying a single member's details.

  ## Features
  - Display all member information in grouped sections
  - Tab navigation for future features (Payments)
  - Show custom field values with type-based formatting
  - Navigate to edit form
  - Return to member list

  ## Sections
  - Personal Data: Name, address, contact information, membership dates, notes
  - Custom Fields: Dynamic fields in uniform grid layout (sorted by name)
  - Groups: Links to group detail pages in Personal Data section
  - Payment Data: Membership fee type and cycle status
  - Membership Fees: Tab showing all membership fee cycles with status management (via MembershipFeesComponent)

  ## Navigation
  - Back to member list
  - Edit member (with return_to parameter for back navigation)
  """
  use MvWeb, :live_view
  import Ash.Query
  import MvWeb.LiveHelpers, only: [current_actor: 1]

  alias MvWeb.Helpers.MembershipFeeHelpers

  @impl true
  def render(assigns) do
    ~H"""
    <Layouts.app flash={@flash} current_user={@current_user}>
      <.header>
        <:leading>
          <.button
            navigate={~p"/members?highlight=#{@member.id}"}
            variant="neutral"
            aria-label={gettext("Back to members list")}
          >
            <.icon name="hero-arrow-left" class="size-4" />
            {gettext("Back")}
          </.button>
        </:leading>
        {MvWeb.Helpers.MemberHelpers.display_name(@member)}
        <:actions>
          <%= if can?(@current_user, :update, @member) do %>
            <.button
              variant="primary"
              navigate={~p"/members/#{@member}/edit?return_to=show"}
              data-testid="member-edit"
            >
              <.icon name="hero-pencil-square" /> {gettext("Edit member")}
            </.button>
          <% end %>
        </:actions>
      </.header>

      <div class="mt-6 space-y-6">
        <%!-- Tab Navigation: surface only behind buttons (inline); tabs-bordered; tabs-lg; both tabs keyboard-focusable --%>
        <div
          role="tablist"
          class="tabs tabs-bordered tabs-lg bg-base-200/60 rounded-box p-1 w-fit"
        >
          <button
            id="member-tab-contact"
            role="tab"
            type="button"
            tabindex="0"
            aria-selected={@active_tab == :contact}
            aria-controls="member-tabpanel-contact"
            class={[
              "tab flex items-center gap-2",
              if(@active_tab == :contact, do: "tab-active", else: "text-base-content/70")
            ]}
            phx-click="switch_tab"
            phx-value-tab="contact"
          >
            <.icon name="hero-identification" class="size-4 shrink-0" />
            {gettext("Contact Data")}
          </button>
          <button
            id="member-tab-membership_fees"
            role="tab"
            type="button"
            tabindex="0"
            aria-selected={@active_tab == :membership_fees}
            aria-controls="member-tabpanel-membership_fees"
            class={[
              "tab flex items-center gap-2",
              if(@active_tab == :membership_fees, do: "tab-active", else: "text-base-content/70")
            ]}
            phx-click="switch_tab"
            phx-value-tab="membership_fees"
          >
            <.icon name="hero-credit-card" class="size-4 shrink-0" />
            {gettext("Membership Fees")}
          </button>
        </div>

        <%= if @active_tab == :contact do %>
          <%!-- Contact Data Tab Content --%>
          <div
            id="member-tabpanel-contact"
            role="tabpanel"
            aria-labelledby="member-tab-contact"
          >
            <%!-- Personal Data and Custom Fields Row --%>
            <div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-6">
              <%!-- Personal Data Section --%>
              <div>
                <.section_box title={gettext("Personal Data")}>
                  <div class="space-y-4">
                    <%!-- Name Row --%>
                    <div class="flex gap-6">
                      <.data_field
                        label={gettext("First Name")}
                        value={@member.first_name}
                        class="w-48"
                      />
                      <.data_field
                        label={gettext("Last Name")}
                        value={@member.last_name}
                        class="w-48"
                      />
                    </div>

                    <%!-- Address --%>
                    <div>
                      <.data_field label={gettext("Address")} value={format_address(@member)} />
                    </div>

                    <%!-- Email --%>
                    <div>
                      <.data_field label={gettext("Email")}>
                        <a
                          href={"mailto:#{MvWeb.MemberLive.Index.format_member_email(@member)}"}
                          class="text-blue-700 hover:text-blue-800 underline"
                        >
                          {@member.email}
                        </a>
                      </.data_field>
                    </div>

                    <%!-- Membership Dates Row --%>
                    <div class="flex gap-6">
                      <.data_field
                        label={gettext("Join Date")}
                        value={format_date(@member.join_date)}
                        class="w-28"
                      />
                      <.data_field
                        label={gettext("Exit Date")}
                        value={format_date(@member.exit_date)}
                        class="w-28"
                      />
                    </div>

                    <%!-- Linked User: only show when current user can see other users (e.g. admin).
                  read_only cannot see linked user, so hide the section to avoid "No user linked" when
                  a user is linked but not visible. --%>
                    <%= if can_access_page?(@current_user, "/users") do %>
                      <div>
                        <.data_field label={gettext("Linked User")}>
                          <%= if @member.user do %>
                            <.link
                              navigate={~p"/users/#{@member.user}"}
                              class="text-blue-700 hover:text-blue-800 underline inline-flex items-center gap-1"
                            >
                              <.icon name="hero-user" class="size-4" />
                              {@member.user.email}
                            </.link>
                          <% else %>
                            <span class="text-base-content/70 italic">
                              {gettext("No user linked")}
                            </span>
                          <% end %>
                        </.data_field>
                      </div>
                    <% end %>

                    <%!-- Groups (in Personal Data) --%>
                    <% groups = @member.groups || [] %>
                    <div>
                      <.data_field label={gettext("Groups")}>
                        <%= if Enum.empty?(groups) do %>
                          <span class="text-base-content/70 italic">{gettext("No groups")}</span>
                        <% else %>
                          <div class="flex flex-wrap gap-2">
                            <%= for group <- groups do %>
                              <.button
                                variant="outline"
                                size="sm"
                                navigate={~p"/groups/#{group.slug}"}
                                aria-label={gettext("Member of group %{name}", name: group.name)}
                              >
                                {group.name}
                              </.button>
                            <% end %>
                          </div>
                        <% end %>
                      </.data_field>
                    </div>

                    <%!-- Notes --%>
                    <%= if @member.notes && String.trim(@member.notes) != "" do %>
                      <div>
                        <.data_field label={gettext("Notes")}>
                          <p class="whitespace-pre-wrap text-base-content/80">{@member.notes}</p>
                        </.data_field>
                      </div>
                    <% end %>
                  </div>
                </.section_box>
              </div>

              <%!-- Custom Fields Section --%>
              <%= if Enum.any?(@custom_fields) do %>
                <div>
                  <.section_box title={gettext("Custom Fields")}>
                    <div class="grid grid-cols-2 gap-4">
                      <%= for custom_field <- @custom_fields do %>
                        <% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>
                        <.data_field label={custom_field.name}>
                          {format_custom_field_value(cfv, custom_field.value_type)}
                        </.data_field>
                      <% end %>
                    </div>
                  </.section_box>
                </div>
              <% end %>
            </div>

            <%!-- Payment Data Section --%>
            <div class="w-full">
              <.section_box title={gettext("Payment Data")}>
                <%= if @member.membership_fee_type do %>
                  <div class="flex gap-6 flex-wrap">
                    <.data_field
                      label={gettext("Type")}
                      value={@member.membership_fee_type.name}
                      class="min-w-32"
                    />
                    <.data_field
                      label={gettext("Membership Fee")}
                      value={MembershipFeeHelpers.format_currency(@member.membership_fee_type.amount)}
                      class="min-w-24"
                    />
                    <.data_field
                      label={gettext("Payment Interval")}
                      value={
                        MembershipFeeHelpers.format_interval(@member.membership_fee_type.interval)
                      }
                      class="min-w-32"
                    />
                    <.data_field label={gettext("Last Cycle")} class="min-w-32">
                      <%= if @member.last_cycle_status do %>
                        <.badge variant={MembershipFeeHelpers.status_variant(@member.last_cycle_status)}>
                          {format_status_label(@member.last_cycle_status)}
                        </.badge>
                      <% else %>
                        <.badge variant="neutral">{gettext("No cycles")}</.badge>
                      <% end %>
                    </.data_field>
                    <.data_field label={gettext("Current Cycle")} class="min-w-36">
                      <%= if @member.current_cycle_status do %>
                        <.badge variant={
                          MembershipFeeHelpers.status_variant(@member.current_cycle_status)
                        }>
                          {format_status_label(@member.current_cycle_status)}
                        </.badge>
                      <% else %>
                        <.badge variant="neutral">{gettext("No cycles")}</.badge>
                      <% end %>
                    </.data_field>
                  </div>
                <% else %>
                  <div class="text-base-content/70 italic">
                    {gettext("No membership fee type assigned")}
                  </div>
                <% end %>
              </.section_box>
            </div>
          </div>
        <% end %>

        <%= if @active_tab == :membership_fees do %>
          <%!-- Membership Fees Tab Content --%>
          <div
            id="member-tabpanel-membership_fees"
            role="tabpanel"
            aria-labelledby="member-tab-membership_fees"
          >
            <.live_component
              module={MvWeb.MemberLive.Show.MembershipFeesComponent}
              id={"membership-fees-#{@member.id}"}
              member={@member}
              current_user={@current_user}
              vereinfacht_receipts={@vereinfacht_receipts}
            />
          </div>
        <% end %>

        <%!-- Danger zone: same section pattern as section_box (h2 outside border) --%>
        <%= if can?(@current_user, :destroy, @member) do %>
          <section class="mt-8 mb-6" aria-labelledby="danger-zone-heading">
            <h2 id="danger-zone-heading" class="text-lg font-semibold mb-3 text-error">
              {gettext("Danger zone")}
            </h2>
            <div class="border border-base-300 rounded-lg p-4 bg-base-100">
              <p class="text-base-content/70 mb-4">
                {gettext(
                  "Deleting this member cannot be undone. All related data (e.g. membership fee cycles) will be removed."
                )}
              </p>
              <.button
                variant="danger"
                phx-click="delete"
                phx-value-id={@member.id}
                data-confirm={
                  gettext("Are you sure you want to delete %{name}? This action cannot be undone.",
                    name: MvWeb.Helpers.MemberHelpers.display_name(@member)
                  )
                }
                data-testid="member-delete"
                aria-label={
                  gettext("Delete member %{name}",
                    name: MvWeb.Helpers.MemberHelpers.display_name(@member)
                  )
                }
              >
                <.icon name="hero-trash" class="size-4" />
                {gettext("Delete member")}
              </.button>
            </div>
          </section>
        <% end %>
      </div>
    </Layouts.app>
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    {:ok,
     socket
     |> assign(:active_tab, :contact)
     |> assign(:vereinfacht_receipts, nil)}
  end

  @impl true
  def handle_params(%{"id" => id}, _, socket) do
    actor = current_actor(socket)

    # Load custom fields once using assign_new to avoid repeated queries
    socket =
      assign_new(socket, :custom_fields, fn ->
        Mv.Membership.CustomField
        |> Ash.Query.sort(name: :asc)
        |> Ash.read!(actor: actor)
      end)

    query =
      Mv.Membership.Member
      |> filter(id == ^id)
      |> load([
        :user,
        :membership_fee_type,
        custom_field_values: [:custom_field],
        membership_fee_cycles: [:membership_fee_type],
        groups: [:id, :name, :slug]
      ])

    member = Ash.read_one!(query, actor: actor)

    # Calculate last and current cycle status from loaded cycles
    last_cycle_status = get_last_cycle_status(member)
    current_cycle_status = get_current_cycle_status(member)

    member =
      member
      |> Map.put(:last_cycle_status, last_cycle_status)
      |> Map.put(:current_cycle_status, current_cycle_status)

    {:noreply,
     socket
     |> assign(:page_title, page_title(socket.assigns.live_action))
     |> assign(:member, member)}
  end

  @impl true
  def handle_event("switch_tab", %{"tab" => "contact"}, socket) do
    {:noreply, assign(socket, :active_tab, :contact)}
  end

  def handle_event("switch_tab", %{"tab" => "membership_fees"}, socket) do
    {:noreply, assign(socket, :active_tab, :membership_fees)}
  end

  @impl true
  def handle_event("delete", %{"id" => id}, socket) do
    member = socket.assigns.member
    actor = current_actor(socket)

    if to_string(id) != to_string(member.id) do
      {:noreply, put_flash(socket, :error, gettext("Member not found"))}
    else
      case Ash.destroy(member, actor: actor) do
        :ok ->
          {:noreply,
           socket
           |> put_flash(:success, gettext("Member deleted successfully"))
           |> push_navigate(to: ~p"/members")}

        {:error, %Ash.Error.Forbidden{}} ->
          {:noreply,
           put_flash(
             socket,
             :error,
             gettext("You do not have permission to delete this member")
           )}

        {:error, error} ->
          require Logger
          Logger.warning("Member delete failed: member_id=#{member.id} error=#{inspect(error)}")
          {:noreply, put_flash(socket, :error, format_error(error))}
      end
    end
  end

  def handle_event("load_vereinfacht_receipts", %{"contact_id" => contact_id}, socket) do
    response =
      case Mv.Vereinfacht.Client.get_contact_with_receipts(contact_id) do
        {:ok, receipts} -> {:ok, receipts}
        {:error, reason} -> {:error, reason}
      end

    {:noreply, assign(socket, :vereinfacht_receipts, response)}
  end

  # Flash set in LiveComponent is not shown in parent layout; child sends this to display flash
  @impl true
  def handle_info({:put_flash, type, message}, socket) do
    {:noreply, put_flash(socket, type, message)}
  end

  # MembershipFeesComponent sends this after cycles are created/deleted/regenerated so parent keeps member in sync
  @impl true
  def handle_info({:member_updated, updated_member}, socket) do
    member =
      updated_member
      |> Map.put(:last_cycle_status, get_last_cycle_status(updated_member))
      |> Map.put(:current_cycle_status, get_current_cycle_status(updated_member))

    {:noreply, assign(socket, :member, member)}
  end

  defp page_title(:show), do: gettext("Show Member")
  defp page_title(:edit), do: gettext("Edit Member")

  defp format_error(%Ash.Error.Invalid{errors: errors}) do
    error_messages =
      Enum.map(errors, fn
        %{field: field, message: message} -> "#{field}: #{message}"
        %{message: message} -> message
        _ -> inspect(errors)
      end)

    Enum.join(error_messages, ", ")
  end

  defp format_error(error), do: inspect(error)

  # -----------------------------------------------------------------
  # Helper Components
  # -----------------------------------------------------------------

  # Renders a section box with border and title.
  attr :title, :string, required: true
  slot :inner_block, required: true

  defp section_box(assigns) do
    ~H"""
    <section class="mb-6">
      <h2 class="text-lg font-semibold mb-3">{@title}</h2>
      <div class="border border-base-300 rounded-lg p-4 bg-base-100">
        {render_slot(@inner_block)}
      </div>
    </section>
    """
  end

  # Renders a labeled data field.
  attr :label, :string, required: true
  attr :value, :string, default: nil
  attr :class, :string, default: ""
  slot :inner_block

  defp data_field(assigns) do
    ~H"""
    <dl class={@class}>
      <dt class="text-sm font-medium text-base-content/70">{@label}</dt>
      <dd class="mt-1 text-base-content">
        <%= if @inner_block != [] do %>
          {render_slot(@inner_block)}
        <% else %>
          {display_value(@value)}
        <% end %>
      </dd>
    </dl>
    """
  end

  # Renders a mailto link if email is present, otherwise renders empty value placeholder
  attr :email, :string, required: true
  attr :display, :string, default: nil

  defp mailto_link(assigns) do
    display_text = assigns.display || assigns.email

    if assigns.email && String.trim(assigns.email) != "" do
      assigns = %{email: assigns.email, display: display_text}

      ~H"""
      <a
        href={"mailto:#{@email}"}
        class="link link-primary"
      >
        {@display}
      </a>
      """
    else
      render_empty_value()
    end
  end

  # -----------------------------------------------------------------
  # Helper Functions
  # -----------------------------------------------------------------

  defp display_value(nil), do: render_empty_value()
  defp display_value(""), do: render_empty_value()
  defp display_value(value), do: value

  defp format_status_label(:paid), do: gettext("Paid")
  defp format_status_label(:unpaid), do: gettext("Unpaid")
  defp format_status_label(:suspended), do: gettext("Suspended")
  defp format_status_label(nil), do: gettext("No status")

  defp get_last_cycle_status(member) do
    case MembershipFeeHelpers.get_last_completed_cycle(member) do
      nil -> nil
      cycle -> cycle.status
    end
  end

  defp get_current_cycle_status(member) do
    case MembershipFeeHelpers.get_current_cycle(member) do
      nil -> nil
      cycle -> cycle.status
    end
  end

  defp format_address(member) do
    street_part =
      [member.street, member.house_number]
      |> Enum.filter(&(&1 && &1 != ""))
      |> Enum.join(" ")

    city_part =
      [member.postal_code, member.city]
      |> Enum.filter(&(&1 && &1 != ""))
      |> Enum.join(" ")

    [member.country, street_part, city_part]
    |> Enum.filter(&(&1 && &1 != ""))
    |> Enum.join(", ")
    |> case do
      "" -> nil
      address -> address
    end
  end

  defp format_date(nil), do: nil

  defp format_date(%Date{} = date) do
    Calendar.strftime(date, "%d.%m.%Y")
  end

  defp format_date(date), do: to_string(date)

  # Finds custom field value for a given custom field id
  # Returns the value (not the CustomFieldValue struct) or nil
  defp find_custom_field_value(nil, _custom_field_id), do: nil

  defp find_custom_field_value(custom_field_values, custom_field_id)
       when is_list(custom_field_values) do
    Enum.find_value(custom_field_values, fn cfv ->
      if cfv.custom_field_id == custom_field_id or
           (cfv.custom_field && cfv.custom_field.id == custom_field_id) do
        cfv.value
      end
    end)
  end

  defp find_custom_field_value(_custom_field_values, _custom_field_id), do: nil

  # Formats custom field value based on type
  # Handles both CustomFieldValue structs and direct values
  defp format_custom_field_value(nil, _type), do: render_empty_value()

  defp format_custom_field_value(%Mv.Membership.CustomFieldValue{} = cfv, value_type) do
    format_custom_field_value(cfv.value, value_type)
  end

  defp format_custom_field_value(%Ash.Union{value: value, type: type}, _expected_type) do
    format_custom_field_value(value, type)
  end

  defp format_custom_field_value(value, :boolean) when is_boolean(value) do
    if value, do: gettext("Yes"), else: gettext("No")
  end

  defp format_custom_field_value(%Date{} = date, :date) do
    Calendar.strftime(date, "%d.%m.%Y")
  end

  defp format_custom_field_value(value, :email) when is_binary(value) do
    if String.trim(value) == "" do
      render_empty_value()
    else
      assigns = %{email: value}

      ~H"""
      <.mailto_link email={@email} display={@email} />
      """
    end
  end

  defp format_custom_field_value(value, :integer) when is_integer(value) do
    Integer.to_string(value)
  end

  defp format_custom_field_value(value, _type) when is_binary(value) do
    if String.trim(value) == "", do: render_empty_value(), else: value
  end

  defp format_custom_field_value(value, _type), do: to_string(value)

  # Renders accessible placeholder for empty values
  # Uses translated text for screen readers while maintaining visual consistency
  # The visual "—" is hidden from screen readers, while the translated text is only visible to screen readers
  defp render_empty_value do
    assigns = %{text: gettext("Not set")}

    ~H"""
    <span class="text-base-content/50 italic">
      <span aria-hidden="true">—</span>
      <span class="sr-only">{@text}</span>
    </span>
    """
  end
end
