/**
 * @vitest-environment happy-dom
 */
import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useAddressValidation } from "./useAddressValidation";
import type { AddressFormData } from "./types";

const mockActionFn = vi.fn();
vi.mock("convex/react", () => ({
  useAction: () => mockActionFn,
}));

vi.mock("../../../../../convex/_generated/api", () => ({
  api: {
    checkoutActions: { validateAddress: "mock-validateAddress-ref" },
  },
}));

const sampleInput: AddressFormData = {
  firstName: "Alice",
  lastName: "Smith",
  phone: "7911123456",
  addressLine1: "10 Downing Street",
  city: "London",
  postalCode: "SW1A 2AA",
  country: "GB",
};

const validResult = {
  isValid: true,
  validationValue: "valid" as const,
  reasons: [],
  addressType: "unknown" as const,
  changedAttributes: [],
  originalAddress: {
    addressLine1: "10 Downing Street",
    city: "London",
    postalCode: "SW1A 2AA",
    country: "GB",
  },
};

const resultWithRecommendation = {
  isValid: false,
  validationValue: "partially_valid" as const,
  reasons: [{ code: "postal_code", description: "Postcode corrected" }],
  addressType: "unknown" as const,
  changedAttributes: ["postalCode"],
  recommendedAddress: {
    addressLine1: "10 Downing Street",
    city: "London",
    postalCode: "SW1A 2AA",
    country: "GB",
    confidenceScore: "high" as const,
    confidenceCode: "postal_data_match",
    confidenceDescription: "Matched via postal data",
  },
  originalAddress: {
    addressLine1: "10 Downing St",
    city: "London",
    postalCode: "SW1A2AA",
    country: "GB",
  },
};

describe("useAddressValidation", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("starts with idle state", () => {
    const { result } = renderHook(() => useAddressValidation());
    expect(result.current.result).toBeNull();
    expect(result.current.isValidating).toBe(false);
    expect(result.current.error).toBeNull();
  });

  it("calls the action with mapped args — no state, additionalInformation, derived name", async () => {
    mockActionFn.mockResolvedValue(validResult);
    const { result } = renderHook(() => useAddressValidation());

    let returnedResult: unknown;
    await act(async () => {
      returnedResult = await result.current.validate(sampleInput);
    });

    expect(mockActionFn).toHaveBeenCalledWith({
      addressLine1: "10 Downing Street",
      additionalInformation: undefined,
      city: "London",
      postalCode: "SW1A 2AA",
      country: "GB",
      name: "Alice Smith",
    });

    expect(result.current.result).toEqual(validResult);
    expect(result.current.isValidating).toBe(false);
    expect(result.current.error).toBeNull();
    expect(returnedResult).toEqual(validResult);
  });

  it("stores result with recommended address", async () => {
    mockActionFn.mockResolvedValue(resultWithRecommendation);
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate(sampleInput);
    });

    expect(result.current.result).toEqual(resultWithRecommendation);
    expect(result.current.result?.recommendedAddress).toBeDefined();
    expect(result.current.result?.recommendedAddress?.addressLine1).toBe("10 Downing Street");
  });

  it("handles action errors and sets error message", async () => {
    mockActionFn.mockRejectedValue(new Error("Address validation service unavailable"));
    const { result } = renderHook(() => useAddressValidation());

    let returnedResult: unknown;
    await act(async () => {
      returnedResult = await result.current.validate(sampleInput);
    });

    expect(result.current.result).toBeNull();
    expect(result.current.isValidating).toBe(false);
    expect(result.current.error).toBe("Address validation service unavailable");
    expect(returnedResult).toBeNull();
  });

  it("handles non-Error exceptions with fallback message", async () => {
    mockActionFn.mockRejectedValue("unexpected string error");
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate(sampleInput);
    });

    expect(result.current.error).toBe("Address validation failed. Please try again.");
  });

  it("reset() clears result and error", async () => {
    mockActionFn.mockResolvedValue(validResult);
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate(sampleInput);
    });
    expect(result.current.result).not.toBeNull();

    act(() => {
      result.current.reset();
    });

    expect(result.current.result).toBeNull();
    expect(result.current.error).toBeNull();
  });

  it("reset() clears error after failed validation", async () => {
    mockActionFn.mockRejectedValue(new Error("Service down"));
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate(sampleInput);
    });
    expect(result.current.error).toBe("Service down");

    act(() => {
      result.current.reset();
    });

    expect(result.current.error).toBeNull();
    expect(result.current.result).toBeNull();
  });

  it("clears previous result before starting a new validation", async () => {
    mockActionFn.mockResolvedValue(validResult);
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate(sampleInput);
    });
    expect(result.current.result).toEqual(validResult);

    mockActionFn.mockResolvedValue(resultWithRecommendation);
    await act(async () => {
      await result.current.validate({ ...sampleInput, addressLine1: "221B Baker Street" });
    });

    expect(result.current.result).toEqual(resultWithRecommendation);
  });

  it("passes additionalInformation when provided", async () => {
    mockActionFn.mockResolvedValue(validResult);
    const { result } = renderHook(() => useAddressValidation());

    const inputWithAdditional: AddressFormData = {
      ...sampleInput,
      additionalInformation: "Flat 4B",
    };

    await act(async () => {
      await result.current.validate(inputWithAdditional);
    });

    expect(mockActionFn).toHaveBeenCalledWith(
      expect.objectContaining({ additionalInformation: "Flat 4B" }),
    );
  });

  it("derives name from firstName and lastName", async () => {
    mockActionFn.mockResolvedValue(validResult);
    const { result } = renderHook(() => useAddressValidation());

    await act(async () => {
      await result.current.validate({
        ...sampleInput,
        firstName: "Margaret",
        lastName: "Thatcher",
      });
    });

    expect(mockActionFn).toHaveBeenCalledWith(
      expect.objectContaining({ name: "Margaret Thatcher" }),
    );
  });
});
