defmodule IbanEx.ParserTest do
  @moduledoc """
  Comprehensive test coverage for IbanEx.Parser module.
  Based on Test Coverage Improvement Plan - Phase 1: Critical Coverage.
  """

  use ExUnit.Case, async: true

  alias IbanEx.{Parser, TestData, Iban}

  describe "parse/1 - comprehensive parsing" do
    test "parses all 105 registry valid IBANs successfully" do
      TestData.valid_ibans()
      |> Enum.each(fn iban ->
        assert {:ok, %Iban{}} = Parser.parse(iban), "Failed to parse: #{iban}"
      end)
    end

    test "parses shortest IBAN (Norway, 15 chars)" do
      {:ok, iban} = Parser.parse("NO9386011117947")

      assert iban.country_code == "NO"
      assert iban.check_digits == "93"
      assert iban.bank_code == "8601"
      assert iban.account_number == "111794"
      assert iban.branch_code == nil
      assert String.length(iban.iban) == 15
    end

    test "parses longest IBAN (Russia, 33 chars)" do
      longest = TestData.edge_cases().longest
      {:ok, iban} = Parser.parse(longest)

      assert iban.country_code == "RU"
      assert String.length(iban.iban) == 33
      assert String.length(iban.bank_code) == 9
      assert String.length(iban.branch_code) == 5
      assert String.length(iban.account_number) == 15
    end

    test "parses IBAN with branch code (France)" do
      {:ok, iban} = Parser.parse("FR1420041010050500013M02606")

      assert iban.country_code == "FR"
      assert iban.check_digits == "14"
      assert iban.bank_code == "20041"
      assert iban.branch_code == "01005"
      assert iban.account_number == "0500013M026"
      assert iban.national_check == "06"
    end

    test "parses IBAN with national check (Italy)" do
      {:ok, iban} = Parser.parse("IT60X0542811101000000123456")

      assert iban.country_code == "IT"
      assert iban.check_digits == "60"
      assert iban.national_check == "X"
      assert iban.bank_code == "05428"
      assert iban.branch_code == "11101"
      assert iban.account_number == "000000123456"
    end

    test "parses IBAN with alphanumeric BBAN (Qatar)" do
      {:ok, iban} = Parser.parse("QA58DOHB00001234567890ABCDEFG")

      assert iban.country_code == "QA"
      assert iban.check_digits == "58"
      assert iban.bank_code == "DOHB"
      assert iban.account_number == "00001234567890ABCDEFG"
    end

    test "parses electronic format" do
      {:ok, iban} = Parser.parse("DE89370400440532013000")

      assert iban.iban == "DE89370400440532013000"
      assert iban.country_code == "DE"
    end

    test "parses print format with spaces" do
      {:ok, iban} = Parser.parse("DE89 3704 0044 0532 0130 00")

      # Parser should normalize to electronic format
      assert iban.iban == "DE89370400440532013000"
      assert iban.country_code == "DE"
    end

    test "returns error for invalid checksum" do
      assert {:error, _} = Parser.parse("DE00370400440532013000")
    end

    test "returns error for unsupported country code" do
      assert {:error, _} = Parser.parse("XX89370400440532013000")
    end

    test "returns error for invalid length (too short)" do
      assert {:error, _} = Parser.parse("DE8937040044053201300")
    end

    test "returns error for invalid length (too long)" do
      assert {:error, _} = Parser.parse("DE89370400440532013000XXX")
    end

    test "returns error for invalid characters" do
      assert {:error, _} = Parser.parse("DE89370400440532013Ї00")
    end

    test "returns error for empty string" do
      assert {:error, _} = Parser.parse("")
    end

    test "returns error for nil" do
      assert {:error, _} = Parser.parse(nil)
    end
  end

  describe "parse/1 - BBAN component extraction" do
    test "extracts bank code for all countries" do
      TestData.valid_ibans()
      |> Enum.each(fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_binary(parsed.bank_code), "Missing bank code for: #{iban}"
        assert String.length(parsed.bank_code) > 0
      end)
    end

    test "extracts branch code for countries that have it" do
      # Countries with branch codes (55 countries)
      ibans_with_branch = TestData.ibans_with(has_branch_code: true)

      Enum.each(ibans_with_branch, fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_binary(parsed.branch_code), "Missing branch code for: #{iban}"
        assert String.length(parsed.branch_code) > 0
      end)
    end

    test "sets branch code to nil for countries without it" do
      ibans_without_branch = TestData.ibans_with(has_branch_code: false)

      Enum.each(ibans_without_branch, fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_nil(parsed.branch_code), "Unexpected branch code for: #{iban}"
      end)
    end

    test "extracts national check for countries that have it" do
      ibans_with_check = TestData.ibans_with(has_national_check: true)

      Enum.each(ibans_with_check, fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_binary(parsed.national_check), "Missing national check for: #{iban}"
        assert String.length(parsed.national_check) > 0
      end)
    end

    test "sets national check to nil for countries without it" do
      ibans_without_check = TestData.ibans_with(has_national_check: false)

      Enum.each(ibans_without_check, fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_nil(parsed.national_check), "Unexpected national check for: #{iban}"
      end)
    end

    test "extracts account number for all countries" do
      TestData.valid_ibans()
      |> Enum.each(fn iban ->
        {:ok, parsed} = Parser.parse(iban)
        assert is_binary(parsed.account_number), "Missing account number for: #{iban}"
        assert String.length(parsed.account_number) > 0
      end)
    end
  end

  describe "parse/1 - position calculations" do
    test "correctly calculates positions for Germany (simple structure)" do
      {:ok, iban} = Parser.parse("DE89370400440532013000")

      # BBAN: 370400440532013000
      # Bank code (8n): 37040044
      # Account (10n): 0532013000
      assert iban.bank_code == "37040044"
      assert iban.account_number == "0532013000"
      assert iban.branch_code == nil
    end

    test "correctly calculates positions for France (complex structure)" do
      {:ok, iban} = Parser.parse("FR1420041010050500013M02606")

      # BBAN: 20041010050500013M02606
      # Bank (5n): 20041
      # Branch (5n): 01005
      # Account (11c): 0500013M026
      # Check (2n): 06
      assert iban.bank_code == "20041"
      assert iban.branch_code == "01005"
      assert iban.account_number == "0500013M026"
      assert iban.national_check == "06"
    end

    test "correctly calculates positions for GB (6-digit sort code)" do
      {:ok, iban} = Parser.parse("GB29NWBK60161331926819")

      # Bank (4a): NWBK
      # Branch/Sort (6n): 601613
      # Account (8n): 31926819
      assert iban.bank_code == "NWBK"
      assert iban.branch_code == "601613"
      assert iban.account_number == "31926819"
    end

    test "correctly calculates positions for Brazil (complex alphanumeric)" do
      {:ok, iban} = Parser.parse("BR1800360305000010009795493C1")

      # Brazil has: bank (8n) + branch (5n) + account (10n) + type (1a) + owner (1c)
      assert String.length(iban.bank_code) == 8
      assert String.length(iban.branch_code) == 5
      # Account number includes type and owner
      assert String.length(iban.account_number) > 10
    end
  end

  describe "parse/1 - edge cases" do
    test "handles all-numeric BBANs" do
      # Germany has all-numeric BBAN
      {:ok, iban} = Parser.parse("DE89370400440532013000")

      assert iban.bank_code =~ ~r/^\d+$/
      assert iban.account_number =~ ~r/^\d+$/
    end

    test "handles alphanumeric BBANs" do
      # Qatar has alphanumeric BBAN
      {:ok, iban} = Parser.parse("QA58DOHB00001234567890ABCDEFG")

      assert iban.bank_code == "DOHB"
      assert iban.account_number =~ ~r/[A-Z]/
    end

    test "handles IBANs with letters at the end" do
      # Brazil ends with letter
      {:ok, iban} = Parser.parse("BR1800360305000010009795493C1")

      assert String.ends_with?(iban.account_number, "1") or
               String.ends_with?(iban.iban, "1")
    end

    test "handles minimum length IBAN (15 chars)" do
      {:ok, iban} = Parser.parse("NO9386011117947")

      assert String.length(iban.iban) == 15
      assert iban.country_code == "NO"
      assert String.length(iban.bank_code) == 4
      assert String.length(iban.account_number) == 6
    end

    test "handles maximum length IBAN (33 chars)" do
      longest = TestData.edge_cases().longest
      {:ok, iban} = Parser.parse(longest)

      assert String.length(iban.iban) == 33
      assert iban.country_code == "RU"
    end

    test "handles IBANs from all length categories" do
      length_samples = [
        # Shortest
        15,
        # Short
        18,
        # Medium-short (most common)
        22,
        # Medium-long (most common)
        27,
        # Long
        29,
        # Longest
        33
]

      Enum.each(length_samples, fn target_length ->
        ibans = TestData.ibans_with(length: target_length)

        if length(ibans) > 0 do
          iban = List.first(ibans)
          assert {:ok, parsed} = Parser.parse(iban)
          assert String.length(parsed.iban) == target_length
        end
      end)
    end
  end

  describe "parse/1 - normalization" do
    test "normalizes print format to electronic format" do
      print_format = "DE89 3704 0044 0532 0130 00"
      electronic_format = "DE89370400440532013000"

      {:ok, from_print} = Parser.parse(print_format)
      {:ok, from_electronic} = Parser.parse(electronic_format)

      assert from_print.iban == from_electronic.iban
      assert from_print == from_electronic
    end

    test "removes whitespace from input" do
      with_spaces = "FR14 2004 1010 0505 0001 3M02 606"
      {:ok, iban} = Parser.parse(with_spaces)

      refute String.contains?(iban.iban, " ")
    end

    test "preserves original electronic format when no spaces" do
      original = "DE89370400440532013000"
      {:ok, iban} = Parser.parse(original)

      assert iban.iban == original
    end
  end

  describe "parse/1 - error handling" do
    test "returns descriptive error for unsupported country" do
      assert {:error, reason} = Parser.parse("XX89370400440532013000")
      assert reason != nil
    end

    test "returns descriptive error for invalid length" do
      assert {:error, reason} = Parser.parse("DE8937040044053201")
      assert reason != nil
    end

    test "returns descriptive error for invalid checksum" do
      assert {:error, reason} = Parser.parse("DE00370400440532013000")
      assert reason != nil
    end

    test "returns descriptive error for invalid characters" do
      assert {:error, reason} = Parser.parse("DE89370400440532013Ї00")
      assert reason != nil
    end

    test "returns descriptive error for empty input" do
      assert {:error, reason} = Parser.parse("")
      assert reason != nil
    end

    test "returns descriptive error for nil input" do
      assert {:error, reason} = Parser.parse(nil)
      assert reason != nil
    end
  end

  describe "parse/1 - SEPA countries" do
    test "parses all 53 SEPA country IBANs" do
      sepa_ibans = TestData.valid_ibans(sepa_only: true)

      assert length(sepa_ibans) == 53

      Enum.each(sepa_ibans, fn iban ->
        assert {:ok, %Iban{}} = Parser.parse(iban), "SEPA parsing failed: #{iban}"
      end)
    end

    test "parses French territories using FR rules" do
      # French territories: GF, GP, MQ, RE, etc.
      french_territories = [
        "GF",
        "GP",
        "MQ",
        "RE",
        "PF",
        "TF",
        "YT",
        "NC",
        "BL",
        "MF",
        "PM",
        "WF"
]

      Enum.each(french_territories, fn territory ->
        ibans = TestData.valid_ibans(country: territory)

        if length(ibans) > 0 do
          iban = List.first(ibans)
          assert {:ok, parsed} = Parser.parse(iban)
          assert parsed.country_code == territory
          # Should follow FR structure
          assert String.length(parsed.iban) == 27
        end
      end)
    end
  end

  describe "parse/1 - registry compliance" do
    test "parsed IBANs match registry specifications" do
      TestData.all_country_codes()
      |> Enum.each(fn country_code ->
        spec = TestData.country_spec(country_code)
        [iban] = TestData.valid_ibans(country: country_code)

        {:ok, parsed} = Parser.parse(iban)

        assert String.length(parsed.iban) == spec["iban_length"],
               "Length mismatch for #{country_code}"

        assert parsed.country_code == country_code
      end)
    end

    test "BBAN components match registry positions" do
      # Test a sample of countries with different structures
      test_countries = ["DE", "FR", "GB", "IT", "ES", "BR", "NO", "RU"]

      Enum.each(test_countries, fn country_code ->
        spec = TestData.country_spec(country_code)
        [iban] = TestData.valid_ibans(country: country_code)

        {:ok, parsed} = Parser.parse(iban)

        # Verify bank code length matches spec
        bank_positions = spec["positions"]["bank_code"]
        expected_bank_length = bank_positions["end"] - bank_positions["start"]

        if expected_bank_length > 0 do
          assert String.length(parsed.bank_code) == expected_bank_length,
                 "Bank code length mismatch for #{country_code}"
        end
      end)
    end
  end
end
