defmodule Mv.Membership.Import.MemberCSVTest do
  use Mv.DataCase, async: true

  alias Mv.Membership.Import.MemberCSV

  describe "Error struct" do
    test "Error struct exists with required fields" do
      # This will fail at runtime if the struct doesn't exist
      # We use struct/2 to create the struct at runtime
      error =
        struct(MemberCSV.Error, %{
          csv_line_number: 5,
          field: :email,
          message: "is not a valid email"
        })

      assert error.csv_line_number == 5
      assert error.field == :email
      assert error.message == "is not a valid email"
    end

    test "Error struct allows nil field" do
      # This will fail at runtime if the struct doesn't exist
      error =
        struct(MemberCSV.Error, %{
          csv_line_number: 10,
          field: nil,
          message: "Row is empty"
        })

      assert error.csv_line_number == 10
      assert error.field == nil
      assert error.message == "Row is empty"
    end
  end

  describe "prepare/2" do
    test "accepts file_content and opts and returns tagged tuple" do
      file_content = "email\njohn@example.com"
      opts = []

      result = MemberCSV.prepare(file_content, opts)
      assert match?({:ok, _}, result) or match?({:error, _}, result)
    end

    test "returns {:ok, import_state} on success" do
      file_content = "email\njohn@example.com"
      opts = []

      assert {:ok, import_state} = MemberCSV.prepare(file_content, opts)

      # Check that import_state contains expected fields
      assert Map.has_key?(import_state, :chunks)
      assert Map.has_key?(import_state, :column_map)
      assert Map.has_key?(import_state, :custom_field_map)
      assert Map.has_key?(import_state, :warnings)
      assert import_state.column_map[:email] == 0
      assert import_state.chunks != []
    end

    test "returns {:error, reason} on failure" do
      file_content = ""
      opts = []

      assert {:error, _reason} = MemberCSV.prepare(file_content, opts)
    end
  end

  describe "process_chunk/4" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      %{actor: system_actor}
    end

    test "accepts chunk_rows_with_lines, column_map, custom_field_map, and opts and returns tagged tuple",
         %{
           actor: actor
         } do
      chunk_rows_with_lines = [{2, %{member: %{email: "john@example.com"}, custom: %{}}}]
      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      result = MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)
      assert match?({:ok, _}, result) or match?({:error, _}, result)
    end

    test "creates member successfully with valid data", %{actor: actor} do
      chunk_rows_with_lines = [
        {2, %{member: %{email: "john@example.com", first_name: "John"}, custom: %{}}}
      ]

      column_map = %{email: 0, first_name: 1}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 1
      assert chunk_result.failed == 0
      assert chunk_result.errors == []

      # Verify member was created
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      members = Mv.Membership.list_members!(actor: system_actor)
      assert Enum.any?(members, &(&1.email == "john@example.com"))
    end

    test "returns error for invalid email", %{actor: actor} do
      chunk_rows_with_lines = [
        {2, %{member: %{email: "invalid-email"}, custom: %{}}}
      ]

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 2
      assert error.field == :email
      # Error message should come from validate_row (Gettext-backed)
      assert is_binary(error.message)
      assert error.message != ""
    end

    test "returns error for missing email", %{actor: actor} do
      chunk_rows_with_lines = [
        {2, %{member: %{}, custom: %{}}}
      ]

      column_map = %{}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 2
      assert error.field == :email
      assert is_binary(error.message)
    end

    test "returns error for whitespace-only email", %{actor: actor} do
      chunk_rows_with_lines = [
        {3, %{member: %{email: "   "}, custom: %{}}}
      ]

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 3
      assert error.field == :email
    end

    test "returns error for duplicate email", %{actor: actor} do
      # Create existing member first
      {:ok, _existing} =
        Mv.Membership.create_member(%{email: "duplicate@example.com", first_name: "Existing"},
          actor: actor
        )

      chunk_rows_with_lines = [
        {2, %{member: %{email: "duplicate@example.com", first_name: "New"}, custom: %{}}}
      ]

      column_map = %{email: 0, first_name: 1}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 2
      assert error.field == :email
      assert error.message =~ "email" or error.message =~ "duplicate" or error.message =~ "unique"
    end

    test "creates member with custom field values", %{actor: actor} do
      # Create custom field first
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Phone",
          value_type: :string
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {2,
         %{
           member: %{email: "withcustom@example.com"},
           custom: %{to_string(custom_field.id) => "123-456-7890"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 1
      assert chunk_result.failed == 0

      # Verify member and custom field value were created
      members = Mv.Membership.list_members!(actor: actor)
      member = Enum.find(members, &(&1.email == "withcustom@example.com"))
      assert member != nil

      {:ok, member_with_cf} = Ash.load(member, :custom_field_values, actor: actor)
      assert length(member_with_cf.custom_field_values) == 1
      cfv = List.first(member_with_cf.custom_field_values)
      assert cfv.custom_field_id == custom_field.id
      assert cfv.value.value == "123-456-7890"
    end

    test "handles multiple rows with mixed success and failure", %{actor: actor} do
      chunk_rows_with_lines = [
        {2, %{member: %{email: "valid1@example.com"}, custom: %{}}},
        {3, %{member: %{email: "invalid-email"}, custom: %{}}},
        {4, %{member: %{email: "valid2@example.com"}, custom: %{}}}
      ]

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 2
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 3
      assert error.field == :email
      # Error should come from validate_row, not from DB insert
      assert is_binary(error.message)
    end

    test "preserves CSV line numbers in errors", %{actor: actor} do
      chunk_rows_with_lines = [
        {5, %{member: %{email: "invalid"}, custom: %{}}},
        {10, %{member: %{email: "also-invalid"}, custom: %{}}}
      ]

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.failed == 2
      assert length(chunk_result.errors) == 2

      line_numbers = Enum.map(chunk_result.errors, & &1.csv_line_number)
      assert 5 in line_numbers
      assert 10 in line_numbers
    end

    test "returns {:ok, chunk_result} on success", %{actor: actor} do
      chunk_rows_with_lines = [{2, %{member: %{email: "test@example.com"}, custom: %{}}}]
      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      # Check that chunk_result contains expected fields
      assert Map.has_key?(chunk_result, :inserted)
      assert Map.has_key?(chunk_result, :failed)
      assert Map.has_key?(chunk_result, :errors)
      assert is_integer(chunk_result.inserted)
      assert is_integer(chunk_result.failed)
      assert is_list(chunk_result.errors)
    end

    test "returns {:ok, _} with zero counts for empty chunk", %{actor: actor} do
      chunk_rows_with_lines = []
      column_map = %{}
      custom_field_map = %{}
      opts = [actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 0
      assert chunk_result.errors == []
    end

    test "error capping collects exactly 50 errors", %{actor: actor} do
      # Create 50 rows with invalid emails
      chunk_rows_with_lines =
        1..50
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 0, max_errors: 50, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 50
      assert length(chunk_result.errors) == 50
    end

    test "error capping collects only first 50 errors when more than 50 errors occur", %{
      actor: actor
    } do
      # Create 60 rows with invalid emails
      chunk_rows_with_lines =
        1..60
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 0, max_errors: 50, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 60
      assert length(chunk_result.errors) == 50
    end

    test "error capping respects existing_error_count", %{actor: actor} do
      # Create 30 rows with invalid emails
      chunk_rows_with_lines =
        1..30
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 25, max_errors: 50, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 30
      # Should only collect 25 errors (25 existing + 25 new = 50 limit)
      assert length(chunk_result.errors) == 25
    end

    test "error capping collects no errors when limit already reached", %{actor: actor} do
      # Create 10 rows with invalid emails
      chunk_rows_with_lines =
        1..10
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 50, max_errors: 50, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 10
      assert chunk_result.errors == []
    end

    test "error capping with mixed success and failure", %{actor: actor} do
      # Create 100 rows: 30 valid, 70 invalid
      valid_rows =
        1..30
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "valid#{i}@example.com"}, custom: %{}}}
        end)

      invalid_rows =
        31..100
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      chunk_rows_with_lines = valid_rows ++ invalid_rows

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 0, max_errors: 50, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 30
      assert chunk_result.failed == 70
      # Should only collect 50 errors (limit reached)
      assert length(chunk_result.errors) == 50
    end

    test "error capping with custom max_errors", %{actor: actor} do
      # Create 20 rows with invalid emails
      chunk_rows_with_lines =
        1..20
        |> Enum.map(fn i ->
          {i + 1, %{member: %{email: "invalid-email-#{i}"}, custom: %{}}}
        end)

      column_map = %{email: 0}
      custom_field_map = %{}
      opts = [existing_error_count: 0, max_errors: 10, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 20
      assert length(chunk_result.errors) == 10
    end
  end

  describe "validate_row/3" do
    test "returns error when email is missing" do
      row_map = %{member: %{}, custom: %{}}
      csv_line_number = 5
      opts = []

      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert %MemberCSV.Error{} = error
      assert error.csv_line_number == 5
      assert error.field == :email
      assert error.message != nil
      assert error.message != ""
    end

    test "returns error when email is only whitespace" do
      row_map = %{member: %{email: "   "}, custom: %{}}
      csv_line_number = 3
      opts = []

      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert %MemberCSV.Error{} = error
      assert error.csv_line_number == 3
      assert error.field == :email
      assert error.message != nil
    end

    test "returns error when email is nil" do
      row_map = %{member: %{email: nil}, custom: %{}}
      csv_line_number = 7
      opts = []

      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert %MemberCSV.Error{} = error
      assert error.csv_line_number == 7
      assert error.field == :email
    end

    test "returns error when email format is invalid" do
      row_map = %{member: %{email: "invalid-email"}, custom: %{}}
      csv_line_number = 4
      opts = []

      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert %MemberCSV.Error{} = error
      assert error.csv_line_number == 4
      assert error.field == :email
      assert error.message != nil
    end

    test "returns {:ok, trimmed_row_map} when email is valid with whitespace" do
      row_map = %{member: %{email: "  john@example.com  "}, custom: %{}}
      csv_line_number = 2
      opts = []

      assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert trimmed_row_map.member.email == "john@example.com"
    end

    test "returns {:ok, trimmed_row_map} when email is valid without whitespace" do
      row_map = %{member: %{email: "john@example.com"}, custom: %{}}
      csv_line_number = 2
      opts = []

      assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert trimmed_row_map.member.email == "john@example.com"
    end

    test "trims all string values in member map" do
      row_map = %{
        member: %{
          email: "  john@example.com  ",
          first_name: "  John  ",
          last_name: "  Doe  "
        },
        custom: %{}
      }

      csv_line_number = 2
      opts = []

      assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert trimmed_row_map.member.email == "john@example.com"
      assert trimmed_row_map.member.first_name == "John"
      assert trimmed_row_map.member.last_name == "Doe"
    end

    test "preserves custom map unchanged" do
      row_map = %{
        member: %{email: "john@example.com"},
        custom: %{"field1" => "value1"}
      }

      csv_line_number = 2
      opts = []

      assert {:ok, trimmed_row_map} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert trimmed_row_map.custom == %{"field1" => "value1"}
    end

    test "uses Gettext for error messages" do
      row_map = %{member: %{}, custom: %{}}
      csv_line_number = 5
      opts = []

      # Test with default locale (should work)
      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert is_binary(error.message)

      # Test with German locale
      Gettext.put_locale(MvWeb.Gettext, "de")
      assert {:error, error_de} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert is_binary(error_de.message)

      # Test with English locale
      Gettext.put_locale(MvWeb.Gettext, "en")
      assert {:error, error_en} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert is_binary(error_en.message)

      # Reset to default
      Gettext.put_locale(MvWeb.Gettext, "en")
    end

    test "handles empty opts gracefully" do
      row_map = %{member: %{email: "john@example.com"}, custom: %{}}
      csv_line_number = 2
      opts = []

      assert {:ok, _} = MemberCSV.validate_row(row_map, csv_line_number, opts)
    end

    test "handles missing member key gracefully" do
      row_map = %{custom: %{}}
      csv_line_number = 3
      opts = []

      assert {:error, error} = MemberCSV.validate_row(row_map, csv_line_number, opts)
      assert %MemberCSV.Error{} = error
      assert error.csv_line_number == 3
    end
  end

  describe "custom field import" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()
      %{actor: system_actor}
    end

    test "creates member with valid integer custom field value", %{actor: actor} do
      # Create integer custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Alter",
          value_type: :integer
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {2,
         %{
           member: %{email: "withage@example.com"},
           custom: %{to_string(custom_field.id) => "25"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 1
      assert chunk_result.failed == 0

      # Verify member and custom field value were created
      members = Mv.Membership.list_members!(actor: actor)
      member = Enum.find(members, &(&1.email == "withage@example.com"))
      assert member != nil

      {:ok, member_with_cf} = Ash.load(member, :custom_field_values, actor: actor)
      assert length(member_with_cf.custom_field_values) == 1
      cfv = List.first(member_with_cf.custom_field_values)
      assert cfv.custom_field_id == custom_field.id
      assert cfv.value.value == 25
      assert cfv.value.type == :integer
    end

    test "returns error for invalid integer custom field value", %{actor: actor} do
      # Create integer custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Alter",
          value_type: :integer
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {2,
         %{
           member: %{email: "invalidage@example.com"},
           custom: %{to_string(custom_field.id) => "abc"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 2
      assert error.message =~ "custom_field: Alter"
      assert error.message =~ "Number"
      assert error.message =~ "abc"
    end

    test "returns error for invalid date custom field value", %{actor: actor} do
      # Create date custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Geburtstag",
          value_type: :date
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {3,
         %{
           member: %{email: "invaliddate@example.com"},
           custom: %{to_string(custom_field.id) => "not-a-date"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 3
      assert error.message =~ "custom_field: Geburtstag"
      assert error.message =~ "Date"
    end

    test "returns error for invalid email custom field value", %{actor: actor} do
      # Create email custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Work Email",
          value_type: :email
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {4,
         %{
           member: %{email: "invalidemailcf@example.com"},
           custom: %{to_string(custom_field.id) => "not-an-email"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 4
      assert error.message =~ "custom_field: Work Email"
      assert error.message =~ "E-Mail"
    end

    test "returns error for invalid boolean custom field value", %{actor: actor} do
      # Create boolean custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Is Active",
          value_type: :boolean
        })
        |> Ash.create(actor: actor)

      chunk_rows_with_lines = [
        {5,
         %{
           member: %{email: "invalidbool@example.com"},
           custom: %{to_string(custom_field.id) => "maybe"}
         }}
      ]

      column_map = %{email: 0}
      custom_field_map = %{to_string(custom_field.id) => 1}

      custom_field_lookup = %{
        to_string(custom_field.id) => %{
          id: custom_field.id,
          value_type: custom_field.value_type,
          name: custom_field.name
        }
      }

      opts = [custom_field_lookup: custom_field_lookup, actor: actor]

      assert {:ok, chunk_result} =
               MemberCSV.process_chunk(chunk_rows_with_lines, column_map, custom_field_map, opts)

      assert chunk_result.inserted == 0
      assert chunk_result.failed == 1
      assert length(chunk_result.errors) == 1

      error = List.first(chunk_result.errors)
      assert error.csv_line_number == 5
      assert error.message =~ "custom_field: Is Active"
      # Error message should indicate boolean/Yes-No validation failure
      assert String.contains?(error.message, "Yes/No") ||
               String.contains?(error.message, "true/false") ||
               String.contains?(error.message, "boolean")
    end
  end

  describe "prepare/2 with custom fields" do
    setup do
      system_actor = Mv.Helpers.SystemActor.get_system_actor()

      # Create a custom field
      {:ok, custom_field} =
        Mv.Membership.CustomField
        |> Ash.Changeset.for_create(:create, %{
          name: "Membership Number",
          value_type: :string
        })
        |> Ash.create(actor: system_actor)

      %{actor: system_actor, custom_field: custom_field}
    end

    test "includes custom field in custom_field_map when header matches", %{
      custom_field: custom_field
    } do
      # CSV with custom field column
      csv_content = "email;Membership Number\njohn@example.com;12345"

      assert {:ok, import_state} = MemberCSV.prepare(csv_content)

      # Check that custom field is mapped
      assert Map.has_key?(import_state.custom_field_map, to_string(custom_field.id))
      assert import_state.column_map[:email] == 0
    end

    test "includes warning for unknown custom field column", %{custom_field: _custom_field} do
      # CSV with unknown custom field column (not matching any existing custom field)
      csv_content = "email;NichtExistierend\njohn@example.com;value"

      assert {:ok, import_state} = MemberCSV.prepare(csv_content)

      # Check that warning is present
      assert import_state.warnings != []
      warning = List.first(import_state.warnings)
      assert warning =~ "NichtExistierend"
      assert warning =~ "ignored"
      assert warning =~ "custom field"

      # Check that unknown column is not in custom_field_map
      assert import_state.custom_field_map == %{}
      # Member import should still succeed
      assert import_state.column_map[:email] == 0
    end

    test "import succeeds even with unknown custom field columns", %{custom_field: _custom_field} do
      # CSV with unknown custom field column
      csv_content = "email;UnknownField\njohn@example.com;value"

      assert {:ok, import_state} = MemberCSV.prepare(csv_content)

      # Import state should be valid
      assert import_state.column_map[:email] == 0
      assert import_state.chunks != []
    end
  end
end
