from datetime import (
    time,
    timedelta,
)

import numpy as np
import pytest

from pandas.errors import (
    OutOfBoundsTimedelta,
    Pandas4Warning,
)

import pandas as pd
import pandas._testing as tm
from pandas.core.arrays import TimedeltaArray

from pandas.tseries.frequencies import to_offset


class TestTimedeltas:
    def test_to_timedelta_mixed_unit_strings(self):
        # https://github.com/pandas-dev/pandas/pull/63196#issuecomment-3595743721
        result = pd.to_timedelta(["1 days 06:05:01.00003", "15.5us"])

        expected = pd.TimedeltaIndex([108_301_000_030_000, 15_500], dtype="m8[ns]")
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_all_nat_unit(self):
        # With all-NaT entries, we get "s" unit
        result = pd.to_timedelta([None])
        assert result.unit == "s"

        result = pd.TimedeltaIndex([None])
        assert result.unit == "s"

    def test_to_timedelta_month_raises(self):
        obj = np.timedelta64(1, "M")

        msg = "Unit M is not supported."
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(obj)
        with pytest.raises(ValueError, match=msg):
            pd.Timedelta(obj)
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta([obj])
        with pytest.raises(ValueError, match=msg):
            pd.TimedeltaIndex([obj])

    def test_to_timedelta_none(self):
        # GH#23055
        assert pd.to_timedelta(None) is pd.NaT

    def test_to_timedelta_dt64_raises(self):
        # Passing datetime64-dtype data to TimedeltaIndex is no longer
        #  supported GH#29794
        msg = r"dtype datetime64\[ns\] cannot be converted to timedelta64\[ns\]"

        ser = pd.Series([pd.NaT], dtype="M8[ns]")
        with pytest.raises(TypeError, match=msg):
            pd.to_timedelta(ser)
        with pytest.raises(TypeError, match=msg):
            ser.to_frame().apply(pd.to_timedelta)

    def test_to_timedelta_readonly(self, writable):
        # GH#34857
        arr = np.array([], dtype=object)
        arr.setflags(write=writable)
        result = pd.to_timedelta(arr)
        expected = pd.to_timedelta([])
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_null(self):
        result = pd.to_timedelta(["", ""])
        assert pd.isna(result).all()

    def test_to_timedelta_same_np_timedelta64(self):
        # pass thru
        result = pd.to_timedelta(np.array([np.timedelta64(1, "s")]))
        expected = pd.Index(np.array([np.timedelta64(1, "s")]))
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_series(self):
        # Series
        expected = pd.Series(
            [timedelta(days=1), timedelta(days=1, seconds=1)], dtype="m8[us]"
        )

        msg = "'d' is deprecated and will be removed in a future version."
        with tm.assert_produces_warning(Pandas4Warning, match=msg):
            result = pd.to_timedelta(pd.Series(["1d", "1days 00:00:01"]))
        tm.assert_series_equal(result, expected)

    def test_to_timedelta_units(self):
        # with units
        result = pd.TimedeltaIndex(
            [np.timedelta64(0, "ns"), np.timedelta64(10, "s").astype("m8[ns]")]
        )
        expected = pd.to_timedelta([0, 10], unit="s").as_unit("ns")
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_mixed_dtype(self):
        # https://github.com/pandas-dev/pandas/issues/64044
        result = pd.to_timedelta(np.array([0.5, 2]), unit="m")
        expected = pd.TimedeltaIndex(
            ["0 days 00:00:30", "0 days 00:02:00"], dtype="timedelta64[ns]", freq=None
        )
        tm.assert_index_equal(result, expected)

    @pytest.mark.parametrize(
        "dtype, unit",
        [
            ["int64", "s"],
            ["int64", "m"],
            ["int64", "h"],
            ["timedelta64[s]", "s"],
            ["timedelta64[D]", "D"],
        ],
    )
    def test_to_timedelta_units_dtypes(self, dtype, unit):
        # arrays of various dtypes
        arr = np.array([1] * 5, dtype=dtype)
        result = pd.to_timedelta(arr, unit=unit)
        exp_dtype = "m8[us]" if dtype == "int64" else "m8[s]"
        expected = pd.TimedeltaIndex([np.timedelta64(1, unit)] * 5, dtype=exp_dtype)
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_oob_non_nano(self):
        arr = np.array([pd.NaT._value + 1], dtype="timedelta64[m]")

        msg = (
            "Cannot convert -9223372036854775807 minutes to "
            r"timedelta64\[s\] without overflow"
        )
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            pd.to_timedelta(arr)

        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            pd.TimedeltaIndex(arr)

        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            TimedeltaArray._from_sequence(arr, dtype="m8[s]")

    @pytest.mark.parametrize("box", [lambda x: x, pd.DataFrame])
    @pytest.mark.parametrize("errors", ["raise", "coerce"])
    def test_to_timedelta_dataframe(self, box, errors):
        # GH 11776
        arg = box(np.arange(10).reshape(2, 5))
        with pytest.raises(TypeError, match="1-d array"):
            pd.to_timedelta(arg, errors=errors)

    def test_to_timedelta_invalid_errors(self):
        # bad value for errors parameter
        msg = "errors must be one of"
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(["foo"], errors="never")

    @pytest.mark.parametrize("arg", [[1, 2], 1])
    def test_to_timedelta_invalid_unit(self, arg):
        # these will error
        msg = "invalid unit abbreviation: foo"
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(arg, unit="foo")

    def test_to_timedelta_time(self):
        # time not supported ATM
        msg = (
            "Value must be Timedelta, string, integer, float, timedelta or convertible"
        )
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(time(second=1))
        assert pd.to_timedelta(time(second=1), errors="coerce") is pd.NaT

    def test_to_timedelta_bad_value(self):
        msg = "Could not convert 'foo' to NumPy timedelta"
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(["foo", "bar"])

    def test_to_timedelta_bad_value_coerce(self):
        tm.assert_index_equal(
            pd.TimedeltaIndex([pd.NaT, pd.NaT]),
            pd.to_timedelta(["foo", "bar"], errors="coerce"),
        )

        tm.assert_index_equal(
            pd.TimedeltaIndex(["1 day", pd.NaT, "1 min"]),
            pd.to_timedelta(["1 day", "bar", "1 min"], errors="coerce"),
        )

    @pytest.mark.parametrize(
        "val, errors",
        [
            ("1M", True),
            ("1 M", True),
            ("1Y", True),
            ("1 Y", True),
            ("1y", True),
            ("1 y", True),
            ("1m", False),
            ("1 m", False),
            ("1 day", False),
            ("2day", False),
        ],
    )
    def test_unambiguous_timedelta_values(self, val, errors):
        # GH36666 Deprecate use of strings denoting units with 'M', 'Y', 'm' or 'y'
        # in pd.to_timedelta
        msg = "Units 'M', 'Y' and 'y' do not represent unambiguous timedelta"
        if errors:
            with pytest.raises(ValueError, match=msg):
                pd.to_timedelta(val)
        else:
            # check it doesn't raise
            pd.to_timedelta(val)

    def test_to_timedelta_via_apply(self):
        # GH 5458
        expected = pd.Series([np.timedelta64(1, "s")], dtype="m8[us]")
        result = pd.Series(["00:00:01"]).apply(pd.to_timedelta)
        tm.assert_series_equal(result, expected)

        result = pd.Series([pd.to_timedelta("00:00:01")])
        tm.assert_series_equal(result, expected)

    def test_to_timedelta_inference_without_warning(self):
        # GH#41731 inference produces a warning in the Series constructor,
        #  but _not_ in to_timedelta
        vals = ["00:00:01", pd.NaT]
        with tm.assert_produces_warning(None):
            result = pd.to_timedelta(vals)

        expected = pd.TimedeltaIndex([pd.Timedelta(seconds=1), pd.NaT], dtype="m8[us]")
        tm.assert_index_equal(result, expected)

    def test_to_timedelta_on_missing_values(self):
        # GH5438
        timedelta_NaT = np.timedelta64("NaT", "ns")

        actual = pd.to_timedelta(pd.Series(["00:00:01", np.nan]))
        expected = pd.Series(
            [np.timedelta64(1000000000, "ns"), timedelta_NaT],
            dtype=f"{tm.ENDIAN}m8[us]",
        )
        tm.assert_series_equal(actual, expected)

        ser = pd.Series(["00:00:01", pd.NaT], dtype="m8[us]")
        actual = pd.to_timedelta(ser)
        tm.assert_series_equal(actual, expected)

    @pytest.mark.parametrize("val", [np.nan, pd.NaT, pd.NA])
    def test_to_timedelta_on_missing_values_scalar(self, val):
        actual = pd.to_timedelta(val)
        assert actual._value == np.timedelta64("NaT", "ns").astype("int64")

    @pytest.mark.parametrize("val", [np.nan, pd.NaT, pd.NA])
    def test_to_timedelta_on_missing_values_list(self, val):
        actual = pd.to_timedelta([val])
        assert actual[0]._value == np.timedelta64("NaT", "ns").astype("int64")

    def test_to_timedelta_float(self):
        # https://github.com/pandas-dev/pandas/issues/25077
        arr = np.arange(0, 1, 1e-6)[-10:]
        result = pd.to_timedelta(arr, unit="s")
        expected_asi8 = np.arange(999990000, 10**9, 1000, dtype="int64")
        tm.assert_numpy_array_equal(result.asi8, expected_asi8)

    def test_to_timedelta_coerce_strings_unit(self):
        arr = np.array([1, 2, "error"], dtype=object)
        result = pd.to_timedelta(arr, unit="ns", errors="coerce")
        expected = pd.to_timedelta([1, 2, pd.NaT], unit="ns")
        tm.assert_index_equal(result, expected)

    @pytest.mark.parametrize(
        "expected_val, result_val", [[timedelta(days=2), 2], [None, None]]
    )
    def test_to_timedelta_nullable_int64_dtype(self, expected_val, result_val):
        # GH 35574
        expected = pd.Series([timedelta(days=1), expected_val], dtype="m8[us]")
        result = pd.to_timedelta(pd.Series([1, result_val], dtype="Int64"), unit="days")

        tm.assert_series_equal(result, expected)

    @pytest.mark.parametrize(
        ("input", "expected"),
        [
            ("8:53:08.71800000001", "8:53:08.718"),
            ("8:53:08.718001", "8:53:08.718001"),
            ("8:53:08.7180000001", "8:53:08.7180000001"),
            ("-8:53:08.71800000001", "-8:53:08.718"),
            ("8:53:08.7180000089", "8:53:08.718000008"),
        ],
    )
    @pytest.mark.parametrize("func", [pd.Timedelta, pd.to_timedelta])
    def test_to_timedelta_precision_over_nanos(self, input, expected, func):
        # GH: 36738
        expected = pd.Timedelta(expected)
        result = func(input)
        assert result == expected

    def test_to_timedelta_zerodim(self, fixed_now_ts):
        # ndarray.item() incorrectly returns int for dt64[ns] and td64[ns]
        dt64 = fixed_now_ts.to_datetime64()
        arg = np.array(dt64)

        msg = (
            "Value must be Timedelta, string, integer, float, timedelta "
            "or convertible, not datetime64"
        )
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(arg)

        arg2 = arg.view("m8[ns]")
        result = pd.to_timedelta(arg2)
        assert isinstance(result, pd.Timedelta)
        assert result._value == dt64.view("i8")

    def test_to_timedelta_numeric_ea(self, any_numeric_ea_dtype):
        # GH#48796
        ser = pd.Series([1, pd.NA], dtype=any_numeric_ea_dtype)
        result = pd.to_timedelta(ser)
        expected = pd.Series([pd.Timedelta(1, unit="ns"), pd.NaT])
        tm.assert_series_equal(result, expected)

    def test_to_timedelta_fraction(self):
        result = pd.to_timedelta(1.0 / 3, unit="h")
        expected = pd.Timedelta("0 days 00:19:59.999999998")
        assert result == expected

    def test_to_timedelta_unit_round_floats(self):
        # When the float is round, we give the requested unit
        #  (or nearest-supported) like we do with integers
        arr = np.array([45.0], dtype=object)
        result = pd.to_timedelta(arr, unit="s")
        expected = pd.to_timedelta([45], unit="s")
        tm.assert_index_equal(result, expected)

        arr2 = arr.astype(np.float64)
        result2 = pd.to_timedelta(arr2, unit="s")
        tm.assert_index_equal(result2, expected)

    def test_to_timedelta_unit_non_round_floats(self):
        # With non-round floats, we have to give nanosecond
        arr = np.array([45.5], dtype=object)
        result = pd.to_timedelta(arr, unit="s")
        assert result.unit == "ns"

        arr2 = arr.astype(np.float64)
        result2 = pd.to_timedelta(arr2, unit="s")
        assert result2.unit == "ns"

    @pytest.mark.parametrize("val", [np.inf, -np.inf])
    def test_to_timedelta_object_inf(self, val):
        # GH#66247 non-finite floats in an object array used to raise a bare
        #  OverflowError from int(item); coerce should give NaT and the
        #  default should raise OutOfBoundsTimedelta.
        arr = np.array([1.0, val, 3.0], dtype=object)

        result = pd.to_timedelta(arr, errors="coerce")
        expected = pd.TimedeltaIndex([1, "NaT", 3])
        tm.assert_index_equal(result, expected)

        with pytest.raises(OutOfBoundsTimedelta, match="without overflow"):
            pd.to_timedelta(arr)

    def test_to_timedelta_unit_mixed_round_and_non_round_floats(self):
        # GH#65150 - round floats mixed with non-round floats should
        # respect the unit for all values
        expected = pd.to_timedelta(["0 days 00:00:01", "0 days 00:00:01.01"]).as_unit(
            "ns"
        )

        result = pd.to_timedelta([1.0, 1.01], unit="s")
        tm.assert_index_equal(result, expected)

        # Also test integers mixed with non-round floats
        result2 = pd.to_timedelta([1, 1.01], unit="s")
        tm.assert_index_equal(result2, expected)

    def test_float_to_timedelta_raise_near_bounds(self):
        # GH#57366
        oneday_in_ns = 1e9 * 60 * 60 * 24
        tdmax_in_days = 2**63 / oneday_in_ns

        # just in bounds
        should_succeed = pd.Series([0, tdmax_in_days - 0.005, -tdmax_in_days + 0.005])
        for val in should_succeed:
            pd.Timedelta(val, unit="D")
        pd.to_timedelta(should_succeed, unit="D")

        # just out of bounds
        should_fail1 = pd.Series([0, tdmax_in_days + 0.005])
        should_fail2 = pd.Series([0, -tdmax_in_days - 0.005])
        arr_msg = "cannot convert input"
        scalar_msg1 = str(tdmax_in_days + 0.005)
        scalar_msg2 = str(-tdmax_in_days - 0.005)
        with pytest.raises(OutOfBoundsTimedelta, match=arr_msg):
            pd.to_timedelta(should_fail1, unit="D")
        with pytest.raises(OutOfBoundsTimedelta, match=scalar_msg1):
            pd.Timedelta(should_fail1[1], unit="D")
        with pytest.raises(OutOfBoundsTimedelta, match=arr_msg):
            pd.to_timedelta(should_fail2, unit="D")
        with pytest.raises(OutOfBoundsTimedelta, match=scalar_msg2):
            pd.Timedelta(should_fail2[1], unit="D")

    def test_float_to_timedelta_near_int64_boundary(self):
        # GH#57366 within a few ULP of +/-2**63 ns the float bound check must be
        # exact. A negative float whose ns value lands just past int64 min must
        # raise instead of silently wrapping to a large positive timedelta...
        wrapped = np.array([-(2**63 + 200) / 1e9], dtype="float64")
        with pytest.raises(OutOfBoundsTimedelta, match="cannot convert input"):
            pd.to_timedelta(wrapped, unit="s")

        # ...and an in-bounds float just shy of int64 max must not spuriously
        # raise (it did for units D/W, diverging from the scalar path).
        oneday_in_ns = 1e9 * 60 * 60 * 24
        val = (2**63 - 1 - 100) / oneday_in_ns
        arr = np.array([val], dtype="float64")
        assert pd.to_timedelta(arr, unit="D")[0] == pd.Timedelta(val, unit="D")

    def test_to_timedelta_day_offset(self):
        # GH#64240
        result = pd.to_timedelta(to_offset("D"))
        expected = pd.Timedelta(1, unit="D").as_unit("s")
        assert result == expected
        assert result.unit == expected.unit

    def test_to_timedelta_day_offset_list(self):
        # GH#64240
        offsets = [to_offset("D"), to_offset("2D")]
        result = pd.to_timedelta(offsets)
        expected = pd.to_timedelta(["1D", "2D"]).as_unit("s")
        tm.assert_index_equal(result, expected)

    @pytest.mark.parametrize("finer", ["ms", "us", "ns"])
    def test_to_timedelta_day_offset_mixed_reso(self, finer):
        # GH#64306 a Day offset mixed with a finer-resolution element must be
        #  rescaled to the array's resolution, not stored as a raw seconds value
        result = pd.to_timedelta(
            [to_offset("2D"), pd.Timedelta(1, unit=finer).as_unit(finer)]
        )
        expected = pd.to_timedelta(["2D", f"1{finer}"]).as_unit(finer)
        tm.assert_index_equal(result, expected)
        assert result[0] == pd.Timedelta(2, unit="D")

    def test_float_to_timedelta_raise_oob_ns(self):
        value = np.float64(2**63)
        arr = np.array([value], dtype=np.float64)

        with pytest.raises(OutOfBoundsTimedelta, match="cannot convert input"):
            pd.to_timedelta(arr, unit="ns")

        msg = r"Cannot cast .* from ns to 'ns' without overflow"
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            pd.Timedelta(value, unit="ns")

    def test_float_to_timedelta_raise_oob_non_ns(self):
        # On ARM, float-to-int64 overflow saturates to INT64_MAX instead
        # of wrapping. The integer shortcut (for all-round float arrays)
        # must exclude values outside the int64 domain to avoid silently
        # producing incorrect results.
        value = np.float64(2**63)
        arr = np.array([value], dtype=np.float64)

        with pytest.raises(OutOfBoundsTimedelta, match="cannot convert input"):
            pd.to_timedelta(arr, unit="s")

    def test_uint64_to_timedelta_raise_oob(self):
        # GH#60677 uint64 values > int64 max overflow silently
        uint64_max = np.iinfo(np.uint64).max

        msg = "Cannot convert input with unit 'ns'"

        # arrays via to_timedelta
        arr = np.array([uint64_max], dtype=np.uint64)
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            pd.to_timedelta(arr, unit="ns")
        for con in [pd.array, pd.Series, pd.Index]:
            arr = con([uint64_max], dtype="UInt64")
            with pytest.raises(OutOfBoundsTimedelta, match=msg):
                pd.to_timedelta(arr, unit="ns")
        # scalar via to_timedelta
        with pytest.raises(OutOfBoundsTimedelta):
            pd.to_timedelta(uint64_max, unit="ns")
        # scalar via Timedelta constructor
        msg = r"Cannot cast .* from ns to 'ns' without overflow"
        with pytest.raises(OutOfBoundsTimedelta, match=msg):
            pd.Timedelta(uint64_max, unit="ns")

    def test_uint64_to_timedelta_coerce(self):
        # GH#60677
        uint64_max = np.iinfo(np.uint64).max

        arr = np.array([0, uint64_max], dtype=np.uint64)
        result = pd.to_timedelta(arr, unit="ns", errors="coerce")
        expected = pd.TimedeltaIndex([pd.Timedelta(0), pd.NaT], dtype="m8[ns]")
        tm.assert_index_equal(result, expected)

        for con in [pd.array, pd.Series, pd.Index]:
            arr = con([0, uint64_max, None], dtype="UInt64")
            result = pd.to_timedelta(arr, unit="ns", errors="coerce")
            expected = pd.TimedeltaIndex(
                [pd.Timedelta(0), pd.NaT, pd.NaT], dtype="m8[ns]"
            )
            if con is pd.Series:
                expected = pd.Series(expected)
            tm.assert_equal(result, expected)

        # scalar
        result = pd.to_timedelta(uint64_max, unit="ns", errors="coerce")
        assert result is pd.NaT

    def test_uint64_to_timedelta_valid(self):
        # GH#60677 valid uint64 values should still work
        arr = np.array([1_000_000, 2_000_000], dtype=np.uint64)
        result = pd.to_timedelta(arr, unit="ns")
        expected = pd.to_timedelta(arr.astype(np.int64), unit="ns")
        tm.assert_index_equal(result, expected)

        arr = pd.array([1_000_000, 2_000_000, None], dtype="UInt64")
        result = pd.to_timedelta(arr, unit="ns")
        expected = expected.append(pd.to_timedelta([pd.NaT]))
        tm.assert_index_equal(result, expected)

    def test_nullable_int_to_timedelta_no_float_precision_loss(
        self, index_or_series_or_array
    ):
        # GH#66988 a nullable int array with a value that is not exactly
        #  representable as float64 should not lose precision by going
        #  through a float64 intermediate when it contains NA values
        value = 2**60 + 1
        arr = index_or_series_or_array([value, None], dtype="Int64")
        result = pd.to_timedelta(arr, unit="ns")
        expected = pd.TimedeltaIndex([pd.Timedelta(value, unit="ns"), pd.NaT])
        if isinstance(arr, pd.Series):
            expected = pd.Series(expected)
        tm.assert_equal(result, expected)

        uarr = index_or_series_or_array([value, None], dtype="UInt64")
        result = pd.to_timedelta(uarr, unit="ns")
        tm.assert_equal(result, expected)

    @pytest.mark.parametrize("unit", ["ps", "fs", "as"])
    def test_sub_nano_unit_raises(self, unit):

        msg = f"invalid unit abbreviation: {unit}"

        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta([1000], unit=unit)

        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(np.array([1000]), unit=unit)

        # scalar via to_datetime
        with pytest.raises(ValueError, match=msg):
            pd.to_timedelta(1000, unit=unit)

        # scalar via Timestamp constructor
        with pytest.raises(ValueError, match=msg):
            pd.Timedelta(1000, unit=unit)


def test_from_numeric_arrow_dtype(any_numeric_ea_dtype):
    # GH 52425
    pytest.importorskip("pyarrow")
    ser = pd.Series([1, 2], dtype=f"{any_numeric_ea_dtype.lower()}[pyarrow]")
    result = pd.to_timedelta(ser)
    expected = pd.Series([1, 2], dtype="timedelta64[ns]")
    tm.assert_series_equal(result, expected)


def test_to_timedelta_np_str():
    # GH#48974 np.str_ should not break timedelta parsing
    result = pd.to_timedelta(np.array(["1 day", "2 days"], dtype=np.str_))
    expected = pd.TimedeltaIndex(["1 days", "2 days"])
    tm.assert_index_equal(result, expected)

    # ISO format
    result = pd.to_timedelta(np.array(["P1DT1H", "P2D"], dtype=np.str_))
    expected = pd.TimedeltaIndex(["1 days 01:00:00", "2 days"])
    tm.assert_index_equal(result, expected)


def test_to_timedelta_scalar_np_str():
    # GH#48974 scalar np.str_ should also not break timedelta parsing
    scalar = np.array(["1 day"])[0]
    assert isinstance(scalar, np.str_)

    expected = pd.Timedelta("1 day")
    assert pd.to_timedelta(scalar) == expected
    assert pd.Timedelta(scalar) == expected

    # ISO format
    assert pd.Timedelta(np.str_("P1DT1H")) == pd.Timedelta("1 days 01:00:00")


@pytest.mark.parametrize("dtype", [np.int16, np.int32, np.uint32, np.int64])
def test_to_timedelta_subint64_with_unit(dtype):
    # GH#56996 NumPy 2 / NEP 50 made `np.int32(x) - py_int` return np.int32,
    # which then overflowed when multiplied by a unit factor that exceeds
    # the dtype's range (e.g. 86_400_000_000_000 ns/day for int32).
    assert pd.to_timedelta(dtype(0), unit="D") == pd.Timedelta(0, unit="D")
    assert pd.to_timedelta(dtype(1), unit="D") == pd.Timedelta(1, unit="D")
    assert pd.Timedelta(dtype(1), unit="D") == pd.Timedelta(1, unit="D")


@pytest.mark.parametrize(
    "dtype", [np.int8, np.int16, np.int32, np.uint8, np.uint16, np.uint32, np.int64]
)
def test_to_timedelta_subint64_with_unit_object_path(dtype):
    # GH#56996 the object-array path reaches cast_from_unit with the raw numpy
    #  scalar, where `frac * m` overflowed the narrow dtype and got reported as
    #  OutOfBoundsTimedelta (or silently coerced to NaT).
    # a list is always converted to object dtype, so this is the object path
    expected = pd.TimedeltaIndex([pd.Timedelta(1, unit="D")]).as_unit("us")

    result = pd.to_timedelta([dtype(1)], unit="D")
    tm.assert_index_equal(result, expected)

    result = pd.to_timedelta([dtype(1)], unit="D", errors="coerce")
    tm.assert_index_equal(result, expected)

    # the issue's own repro, mixing in a Timedelta
    result = pd.to_timedelta([dtype(1), pd.Timedelta(1, "ns")], unit="D")
    expected = pd.TimedeltaIndex([pd.Timedelta(1, unit="D"), pd.Timedelta(1, "ns")])
    tm.assert_index_equal(result, expected)


@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64])
def test_to_timedelta_narrow_float_with_unit(dtype):
    # GH#56996 the base/frac arithmetic used to happen in the input's own float
    #  dtype, so e.g. np.float32(1.5) with unit="D" picked up bogus sub-second
    #  digits, and rounding np.float16 to 13 places overflowed to NaN.
    expected = pd.Timedelta("1 days 12:00:00")

    assert pd.Timedelta(dtype(1.5), unit="D") == expected
    assert pd.to_timedelta(dtype(1.5), unit="D") == expected

    result = pd.to_timedelta([dtype(1.5)], unit="D")
    tm.assert_index_equal(result, pd.TimedeltaIndex([expected]).as_unit("ns"))


@pytest.mark.parametrize(
    "first", [pd.Timedelta(1, "ns"), np.timedelta64(1, "ns"), timedelta(microseconds=1)]
)
# both string sub-branches rescale, so the ISO parser is affected identically
@pytest.mark.parametrize("one_day", ["1 days", "P1D"])
def test_to_timedelta_string_after_finer_element(first, one_day):
    # GH#63196 a string with no sub-microsecond content was unconditionally
    #  rescaled ns->us during the inferring pass, so when an earlier element had
    #  already pushed the array to a finer reso the string landed 1000x too small
    result = pd.to_timedelta([first, one_day])
    expected = pd.TimedeltaIndex([first, pd.Timedelta(1, "D")]).as_unit(
        pd.Timedelta(first).unit
    )
    tm.assert_index_equal(result, expected)


def test_to_timedelta_string_after_finer_element_astype():
    # GH#63196 same defect via the object -> m8 astype path
    ser = pd.Series([pd.Timedelta(1, "ns"), "1 days"], dtype=object)
    result = ser.astype("m8[ns]")
    expected = pd.Series([pd.Timedelta(1, "ns"), pd.Timedelta(1, "D")], dtype="m8[ns]")
    tm.assert_series_equal(result, expected)


@pytest.mark.parametrize(
    "nat_unit, expected_unit",
    [("s", "s"), ("ms", "ms"), ("us", "us"), ("ns", "ns"), ("D", "s")],
)
def test_to_timedelta_td64_nat_with_unit(nat_unit, expected_unit):
    # GH#63018 the NaT sentinel was run through convert_reso, which overflowed
    nat = np.timedelta64("NaT", nat_unit)

    result = pd.to_timedelta([nat])
    assert result[0] is pd.NaT
    # a unit-ful NaT still contributes its unit to the inferred resolution,
    #  matching array_to_datetime; only the bogus conversion is skipped
    assert result.dtype == f"m8[{expected_unit}]"

    result = pd.to_timedelta([nat, pd.Timedelta(1, "ns")])
    expected = pd.TimedeltaIndex([pd.NaT, pd.Timedelta(1, "ns")])
    tm.assert_index_equal(result, expected)


@pytest.mark.parametrize("unit", ["ns", "ms"])
def test_from_timedelta_arrow_dtype(unit):
    # GH 54298
    pytest.importorskip("pyarrow")
    expected = pd.Series([timedelta(1)], dtype=f"duration[{unit}][pyarrow]")
    result = pd.to_timedelta(expected)
    tm.assert_series_equal(result, expected)


@pytest.mark.parametrize(
    "values, unit",
    [
        ([1.5, 1e30], "s"),
        ([1.5, 106751.99616730065], "D"),
        ([2.0**63, 1.0], "ns"),
        ([1.0, np.inf], None),
        ([1.0, -np.inf], None),
    ],
)
@pytest.mark.parametrize(
    "box", [np.array, pd.Series, lambda x: pd.array(x, dtype="Float64")]
)
def test_to_timedelta_float_array_coerce_out_of_bounds(values, unit, box):
    # GH#66823 the float branch raised unconditionally, so errors="coerce" gave
    #  OutOfBoundsTimedelta instead of NaT for float ndarray/Series/masked input,
    #  diverging from the equivalent list (object-dtype) input.
    kwargs = {} if unit is None else {"unit": unit}
    expected = pd.to_timedelta(values, errors="coerce", **kwargs)

    result = pd.to_timedelta(box(values), errors="coerce", **kwargs)
    if isinstance(result, pd.Series):
        tm.assert_numpy_array_equal(result.to_numpy(), expected.to_numpy())
    else:
        tm.assert_index_equal(result, expected)

    # errors="raise" is unaffected
    with pytest.raises(OutOfBoundsTimedelta, match="cannot convert input"):
        pd.to_timedelta(box(values), **kwargs)
