defmodule ToweropsWeb.AccountLive.TotpEnrollmentTest do
  use ToweropsWeb.ConnCase

  import Phoenix.LiveViewTest
  import Towerops.AccountsFixtures

  alias Towerops.Accounts

  describe "TOTP Enrollment Page - unenrolled user" do
    setup do
      # Create user without TOTP enabled (to test enrollment flow)
      user = user_fixture(enable_totp: false)
      %{conn: log_in_user(build_conn(), user), user: user}
    end

    test "generates valid 20-byte secret", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/account/totp-enrollment")

      # Get the base32 secret from the view's assigns
      secret_base32 = :sys.get_state(view.pid).socket.assigns.secret_base32

      # Decode to binary
      secret = Base.decode32!(secret_base32, padding: false, case: :upper)

      # Verify secret is 20 bytes (NimbleTOTP default)
      assert byte_size(secret) == 20

      # Verify we can generate valid codes from it
      code = NimbleTOTP.verification_code(secret)
      assert String.length(code) == 6
      assert NimbleTOTP.valid?(secret, code)
    end

    test "renders enrollment page with QR code", %{conn: conn} do
      {:ok, view, html} = live(conn, ~p"/account/totp-enrollment")

      assert html =~ "Set Up Two-Factor Authentication"
      assert html =~ "Install an Authenticator App"
      assert html =~ "Scan the QR Code"
      assert html =~ "Enter the Code from Your App"

      # Verify QR code is present (as base64 data URI)
      assert view |> element("img[alt='TOTP QR Code']") |> has_element?()
    end

    test "accepts valid TOTP code and enables TOTP", %{conn: conn, user: user} do
      # Create an organization for the user so they redirect to /devices
      {:ok, _org} = Towerops.Organizations.create_organization(%{name: "Test Org"}, user.id)

      {:ok, view, _html} = live(conn, ~p"/account/totp-enrollment")

      # Get the base32 secret from the view's assigns and decode it
      secret_base32 = :sys.get_state(view.pid).socket.assigns.secret_base32
      secret = Base.decode32!(secret_base32, padding: false, case: :upper)

      # Generate a valid code
      code = NimbleTOTP.verification_code(secret)

      # Submit the form
      view
      |> form("form", %{code: code})
      |> render_submit()

      # Render the view to see the recovery codes page
      html = render(view)

      # Should show recovery codes page
      assert html =~ "Save Your Recovery Codes"
      assert html =~ "These codes will only be shown once"
      assert html =~ "Your Recovery Codes"

      # Verify TOTP device was created
      devices = Accounts.list_user_totp_devices(user.id)
      assert length(devices) == 1
      assert hd(devices).name == "Primary Device"

      # Verify recovery codes were generated
      assert Accounts.count_unused_recovery_codes(user.id) == 12

      # Verify TOTP is enabled via new system
      updated_user = Accounts.get_user!(user.id)
      assert Accounts.totp_enabled?(updated_user)

      # Click the confirmation button
      view
      |> element("button", "I've Saved My Recovery Codes")
      |> render_click()

      # Should redirect to devices page (user has organization)
      assert_redirect(view, ~p"/devices")
    end

    test "redirects to orgs page when user has no organizations", %{conn: conn, user: user} do
      {:ok, view, _html} = live(conn, ~p"/account/totp-enrollment")

      # Get the base32 secret from the view's assigns and decode it
      secret_base32 = :sys.get_state(view.pid).socket.assigns.secret_base32
      secret = Base.decode32!(secret_base32, padding: false, case: :upper)

      # Generate a valid code
      code = NimbleTOTP.verification_code(secret)

      # Submit the form
      view
      |> form("form", %{code: code})
      |> render_submit()

      # Render the view to see the recovery codes page
      html = render(view)

      # Should show recovery codes page
      assert html =~ "Save Your Recovery Codes"

      # Verify TOTP was enabled in database
      updated_user = Accounts.get_user!(user.id)
      assert Accounts.totp_enabled?(updated_user)

      # Click the confirmation button
      view
      |> element("button", "I've Saved My Recovery Codes")
      |> render_click()

      # Should redirect to orgs page (user has no organizations)
      assert_redirect(view, ~p"/orgs")
    end

    test "rejects invalid TOTP code", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/account/totp-enrollment")

      # Submit with invalid code
      html =
        view
        |> form("form", %{code: "000000"})
        |> render_submit()

      assert html =~ "Invalid code. Please try again."

      # Should still be on enrollment page (view is still alive, no redirect)
      assert render(view) =~ "Set Up Two-Factor Authentication"
    end

    test "rejects non-numeric code", %{conn: conn} do
      {:ok, view, _html} = live(conn, ~p"/account/totp-enrollment")

      # HTML5 pattern validation would prevent this, but test backend handling
      html =
        view
        |> form("form", %{code: "abcdef"})
        |> render_submit()

      assert html =~ "Invalid code. Please try again."
    end

    test "renders code input field with proper configuration", %{conn: conn} do
      {:ok, _view, html} = live(conn, ~p"/account/totp-enrollment")

      # Verify input field is present with correct attributes
      assert html =~ "id=\"code\""
      assert html =~ "inputmode=\"numeric\""
      assert html =~ "pattern=\"[0-9]{6}\""
      assert html =~ "maxlength=\"6\""
      assert html =~ "placeholder=\"000000\""
    end
  end

  describe "TOTP Enrollment Page - already enrolled user" do
    setup do
      user = user_fixture()
      secret = Accounts.generate_totp_secret()
      {:ok, enrolled_user} = Accounts.enable_totp(user, secret)

      %{conn: log_in_user(build_conn(), enrolled_user), user: enrolled_user}
    end

    test "redirects to devices page if TOTP already enabled", %{conn: conn} do
      {:error, {:redirect, %{to: path}}} = live(conn, ~p"/account/totp-enrollment")

      assert path == ~p"/devices"
    end
  end

  describe "TOTP Enrollment Page - unauthenticated user" do
    test "redirects to login page" do
      conn = build_conn()
      {:error, {:redirect, %{to: path}}} = live(conn, ~p"/account/totp-enrollment")

      assert path == ~p"/users/log-in"
    end
  end
end
