defmodule MvWeb.MemberExportControllerTest do
  use MvWeb.ConnCase, async: true

  alias Mv.Fixtures

  defp csrf_token_from_conn(conn) do
    get_session(conn, "_csrf_token") || csrf_token_from_html(response(conn, 200))
  end

  defp csrf_token_from_html(html) when is_binary(html) do
    case Regex.run(~r/name="csrf-token"\s+content="([^"]+)"/, html) do
      [_, token] -> token
      _ -> nil
    end
  end

  # Export uses humanize_field (e.g. "first_name" -> "First name"); normalize \r\n line endings
  defp export_lines(body) do
    body |> String.split(~r/\r?\n/, trim: true)
  end

  describe "POST /members/export.csv" do
    setup %{conn: conn} do
      # Create 3 members for export tests
      m1 =
        Fixtures.member_fixture(%{
          first_name: "Alice",
          last_name: "One",
          email: "alice.one@example.com"
        })

      m2 =
        Fixtures.member_fixture(%{
          first_name: "Bob",
          last_name: "Two",
          email: "bob.two@example.com"
        })

      m3 =
        Fixtures.member_fixture(%{
          first_name: "Carol",
          last_name: "Three",
          email: "carol.three@example.com"
        })

      %{member1: m1, member2: m2, member3: m3, conn: conn}
    end

    test "exports selected members with specified fields", %{
      conn: conn,
      member1: m1,
      member2: m2
    } do
      payload = %{
        "selected_ids" => [m1.id, m2.id],
        "member_fields" => ["first_name", "last_name", "email"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      assert get_resp_header(conn, "content-type") |> List.first() =~ "text/csv"

      body = response(conn, 200)
      lines = export_lines(body)
      header = hd(lines)

      # Header + 2 data rows (controller uses humanize_field: "first_name" -> "First name")
      assert length(lines) == 3
      assert header =~ "First Name,Last Name,Email"
      assert body =~ "Alice"
      assert body =~ "Bob"
      refute body =~ "Carol"
    end

    test "exports all members when selected_ids is empty", %{
      conn: conn,
      member1: _m1,
      member2: _m2,
      member3: _m3
    } do
      payload = %{
        "selected_ids" => [],
        "member_fields" => ["first_name", "email"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      lines = export_lines(body)

      # Header + at least 3 data rows (controller uses humanize_field)
      assert length(lines) >= 4
      assert body =~ "Alice"
      assert body =~ "Bob"
      assert body =~ "Carol"
    end

    test "filters out unknown member fields from export", %{conn: conn, member1: m1} do
      payload = %{
        "selected_ids" => [m1.id],
        "member_fields" => ["first_name", "unknown_field", "email"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "First Name,Email"
      refute header =~ "unknown_field"
    end

    test "export includes membership_fee_type column when requested", %{
      conn: conn,
      member1: m1
    } do
      payload = %{
        "selected_ids" => [m1.id],
        "member_fields" => ["first_name", "membership_fee_type", "email"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      # Fee Type column is included (label from MemberFields.label(:membership_fee_type))
      assert header =~ "Fee Type"
      assert body =~ "Alice"
    end

    # Regression: when membership_fee_start_date is not in member_fields, Fee Type must still be exported (append fallback)
    test "export includes Fee Type when only first_name and membership_fee_type are requested (no start_date)",
         %{
           conn: conn,
           member1: m1
         } do
      payload = %{
        "selected_ids" => [m1.id],
        "member_fields" => ["first_name", "membership_fee_type"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "Fee Type"
      assert header =~ "First Name"
      assert body =~ "Alice"
    end

    test "export includes membership_fee_status computed field when requested", %{
      conn: conn,
      member1: m1
    } do
      payload = %{
        "selected_ids" => [m1.id],
        "member_fields" => ["first_name"],
        "computed_fields" => ["membership_fee_status"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "First Name,Membership Fee Status"
      assert body =~ "Alice"
    end

    test "exports membership fee status computed field with show_current_cycle option", %{
      conn: conn,
      member1: _m1,
      member2: _m2,
      member3: _m3
    } do
      payload = %{
        "selected_ids" => [],
        "member_fields" => [],
        "computed_fields" => ["membership_fee_status"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil,
        "show_current_cycle" => true
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      lines = export_lines(body)
      header = hd(lines)

      assert header =~ "Membership Fee Status"
    end

    setup %{conn: conn} do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create custom fields for different types
      {:ok, string_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Phone Number",
          value_type: :string
        })
        |> Ash.create(actor: system_actor)

      {:ok, integer_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Membership Number",
          value_type: :integer
        })
        |> Ash.create(actor: system_actor)

      {:ok, boolean_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Active Member",
          value_type: :boolean
        })
        |> Ash.create(actor: system_actor)

      # Create members with custom field values
      {:ok, member_with_string} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "String",
            email: "test.string@example.com"
          },
          actor: system_actor
        )

      {:ok, _cfv_string} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_with_string.id,
          custom_field_id: string_field.id,
          value: "+49 123 456789"
        })
        |> Ash.create(actor: system_actor)

      {:ok, member_with_integer} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "Integer",
            email: "test.integer@example.com"
          },
          actor: system_actor
        )

      {:ok, _cfv_integer} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_with_integer.id,
          custom_field_id: integer_field.id,
          value: 12_345
        })
        |> Ash.create(actor: system_actor)

      {:ok, member_with_boolean} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "Boolean",
            email: "test.boolean@example.com"
          },
          actor: system_actor
        )

      {:ok, _cfv_boolean} =
        Mv.Membership.CustomFieldValue
        |> Ash.Changeset.for_create(:create, %{
          member_id: member_with_boolean.id,
          custom_field_id: boolean_field.id,
          value: true
        })
        |> Ash.create(actor: system_actor)

      {:ok, member_without_value} =
        Mv.Membership.create_member(
          %{
            first_name: "Test",
            last_name: "NoValue",
            email: "test.novalue@example.com"
          },
          actor: system_actor
        )

      %{
        conn: conn,
        string_field: string_field,
        integer_field: integer_field,
        boolean_field: boolean_field,
        member_with_string: member_with_string,
        member_with_integer: member_with_integer,
        member_with_boolean: member_with_boolean,
        member_without_value: member_without_value
      }
    end

    test "export includes custom field column with string value", %{
      conn: conn,
      string_field: string_field,
      member_with_string: member
    } do
      payload = %{
        "selected_ids" => [member.id],
        "member_fields" => ["first_name", "last_name"],
        "custom_field_ids" => [string_field.id],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      lines = export_lines(body)
      header = hd(lines)

      assert header =~ "First Name"
      assert header =~ "Last Name"
      assert header =~ "Phone Number"
      assert body =~ "Test"
      assert body =~ "String"
      assert body =~ "+49 123 456789"
    end

    test "export includes custom field column with integer value", %{
      conn: conn,
      integer_field: integer_field,
      member_with_integer: member
    } do
      payload = %{
        "selected_ids" => [member.id],
        "member_fields" => ["first_name"],
        "custom_field_ids" => [integer_field.id],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "First Name"
      assert header =~ "Membership Number"
      assert body =~ "Test"
      assert body =~ "12345"
    end

    test "export includes custom field column with boolean value", %{
      conn: conn,
      boolean_field: boolean_field,
      member_with_boolean: member
    } do
      payload = %{
        "selected_ids" => [member.id],
        "member_fields" => ["first_name"],
        "custom_field_ids" => [boolean_field.id],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "First Name"
      assert header =~ "Active Member"
      assert body =~ "Test"
      # Boolean values are formatted as "Yes" or "No" by CustomFieldValueFormatter
      assert body =~ "Yes"
    end

    test "export shows empty cell for member without custom field value", %{
      conn: conn,
      string_field: string_field,
      member_without_value: member
    } do
      payload = %{
        "selected_ids" => [member.id],
        "member_fields" => ["first_name", "last_name"],
        "custom_field_ids" => [string_field.id],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      lines = export_lines(body)
      header = hd(lines)
      data_line = Enum.at(lines, 1)

      assert header =~ "Phone Number"
      # Empty custom field value should result in empty cell (two consecutive commas)
      assert data_line =~ "Test,NoValue,"
    end

    test "export includes multiple custom fields in correct order", %{
      conn: conn,
      string_field: string_field,
      integer_field: integer_field,
      boolean_field: boolean_field,
      member_with_string: member
    } do
      payload = %{
        "selected_ids" => [member.id],
        "member_fields" => ["first_name"],
        "custom_field_ids" => [string_field.id, integer_field.id, boolean_field.id],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.csv", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      body = response(conn, 200)
      header = body |> export_lines() |> hd()

      assert header =~ "First Name"
      assert header =~ "Phone Number"
      assert header =~ "Membership Number"
      assert header =~ "Active Member"
      # Verify order: member fields first, then custom fields in the order specified
      header_parts = String.split(header, ",")
      first_name_idx = Enum.find_index(header_parts, &String.contains?(&1, "First Name"))
      phone_idx = Enum.find_index(header_parts, &String.contains?(&1, "Phone Number"))
      membership_idx = Enum.find_index(header_parts, &String.contains?(&1, "Membership Number"))
      active_idx = Enum.find_index(header_parts, &String.contains?(&1, "Active Member"))

      assert first_name_idx < phone_idx
      assert phone_idx < membership_idx
      assert membership_idx < active_idx
    end
  end

  describe "POST /members/export.pdf" do
    test "PDF export includes Fee Type column when requested without membership_fee_start_date",
         %{
           conn: conn
         } do
      m =
        Fixtures.member_fixture(%{first_name: "PDF", last_name: "Test", email: "pdf@example.com"})

      payload = %{
        "selected_ids" => [m.id],
        "member_fields" => ["first_name", "membership_fee_type"],
        "custom_field_ids" => [],
        "query" => nil,
        "sort_field" => nil,
        "sort_order" => nil
      }

      conn = get(conn, "/members")
      csrf_token = csrf_token_from_conn(conn)

      conn =
        post(conn, "/members/export.pdf", %{
          "payload" => Jason.encode!(payload),
          "_csrf_token" => csrf_token
        })

      assert conn.status == 200
      assert get_resp_header(conn, "content-type") |> List.first() =~ "application/pdf"

      body = response(conn, 200)

      # PDF is generated successfully with Fee Type in columns (regression: fee type without start_date)
      assert is_binary(body) and byte_size(body) > 0
    end
  end
end
