import { describe, expect, test } from "@jest/globals" // importing because of type conflict with globals from Cypress
import { render, screen } from "@testing-library/react"
import { type UserEvent, userEvent } from "@testing-library/user-event"
import { FormProvider, useForm } from "react-hook-form"

import { Lang } from "@/constants/languages"
import { dt } from "@/lib/dt"

import { getLocalizedMonthName } from "@/utils/dateFormatting"

import Date from "./index"

jest.mock("react-intl", () => ({
  useIntl: () => ({
    formatMessage: (message: { id: string }) => message.id,
    formatNumber: (value: number) => value,
  }),
}))

interface FormWrapperProps {
  defaultValues: Record<string, unknown>
  children: React.ReactNode
  onSubmit: (data: unknown) => void
}

function FormWrapper({ defaultValues, children, onSubmit }: FormWrapperProps) {
  const methods = useForm({
    defaultValues,
  })
  return (
    <FormProvider {...methods}>
      <form onSubmit={methods.handleSubmit((data) => onSubmit(data))}>
        {children}
        <button type="submit">Submit</button>
      </form>
    </FormProvider>
  )
}

async function selectOption(user: UserEvent, name: RegExp, value: string) {
  // since its not a proper Select element selectOptions from userEvent doesn't work
  const select = screen.queryByRole("button", { name })
  if (select) {
    await user.click(select)

    const option = screen.queryByRole("option", { name: value })
    if (option) {
      await user.click(option)
    } else {
      await user.click(select) // click select again to close it
    }
  }
}

const testCases = [
  {
    description: "date is set and submitted successfully",
    defaultValue: "",
    dateOfBirth: "1987-12-05",
    expectedOutput: {
      dateOfBirth: "1987-12-05",
      year: 1987,
      month: 12,
      day: 5,
    },
  },
  {
    description: "sets default value and submits successfully",
    defaultValue: "2000-01-01",
    dateOfBirth: "",
    expectedOutput: {
      dateOfBirth: "2000-01-01",
      year: 2000,
      month: 1,
      day: 1,
    },
  },
  {
    description: "accepts date exactly 18 years old",
    defaultValue: "",
    dateOfBirth: dt().subtract(18, "year").format("YYYY-MM-DD"),
    expectedOutput: {
      dateOfBirth: dt().subtract(18, "year").format("YYYY-MM-DD"),
    },
  },
  {
    description: "rejects date below 18 years old - by year",
    defaultValue: "",
    dateOfBirth: dt().subtract(17, "year").format("YYYY-MM-DD"),
    expectedOutput: {
      dateOfBirth: "",
    },
  },
  {
    description: "rejects date below 18 years old - by month",
    defaultValue: "",
    dateOfBirth: dt().subtract(18, "year").add(1, "month").format("YYYY-MM-DD"),
    expectedOutput: {
      dateOfBirth: "",
    },
  },
  {
    description: "rejects date below 18 years old - by day",
    defaultValue: "",
    dateOfBirth: dt().subtract(18, "year").add(1, "day").format("YYYY-MM-DD"),
    expectedOutput: {
      dateOfBirth: "",
    },
  },
]

describe("Date input", () => {
  test.each(testCases)(
    "$description",
    async ({ defaultValue, dateOfBirth, expectedOutput }) => {
      const user = userEvent.setup()
      const handleSubmit = jest.fn()

      render(
        <FormWrapper
          defaultValues={{ dateOfBirth: defaultValue }}
          onSubmit={handleSubmit}
        >
          <Date name="dateOfBirth" />
        </FormWrapper>
      )

      const date = dt(dateOfBirth).toDate()
      const year = date.getFullYear()
      const month = date.getMonth() + 1
      const day = date.getDate()

      await selectOption(user, /year/i, year.toString())
      await selectOption(user, /month/i, getLocalizedMonthName(month, Lang.en))
      await selectOption(user, /day/i, day.toString())

      const submitButton = screen.getByRole("button", { name: /submit/i })
      await user.click(submitButton)

      expect(handleSubmit).toHaveBeenCalledWith(
        expect.objectContaining(expectedOutput)
      )
    }
  )
})
