# -*- coding: utf-8 -*-

"""
Tests of the ARIMA class
"""

import numpy as np
import pandas as pd

from pmdarima.arima import ARIMA, auto_arima, AutoARIMA, ARMAtoMA
from pmdarima.arima import _validation as val
from pmdarima.compat.pytest import pytest_error_str
from pmdarima.datasets import load_lynx, load_wineind, load_heartrate

from numpy.random import RandomState
from numpy.testing import assert_array_almost_equal, assert_almost_equal, \
    assert_allclose
from statsmodels import api as sm
from sklearn.metrics import mean_squared_error

import datetime
import joblib
import os
import pickle
import pytest
import tempfile
import time


# initialize the random state
rs = RandomState(42)
y = rs.rand(25)

# > set.seed(123)
# > abc <- rnorm(50, 5, 1)
abc = np.array([4.439524, 4.769823, 6.558708, 5.070508,
                5.129288, 6.715065, 5.460916, 3.734939,
                4.313147, 4.554338, 6.224082, 5.359814,
                5.400771, 5.110683, 4.444159, 6.786913,
                5.497850, 3.033383, 5.701356, 4.527209,
                3.932176, 4.782025, 3.973996, 4.271109,
                4.374961, 3.313307, 5.837787, 5.153373,
                3.861863, 6.253815, 5.426464, 4.704929,
                5.895126, 5.878133, 5.821581, 5.688640,
                5.553918, 4.938088, 4.694037, 4.619529,
                4.305293, 4.792083, 3.734604, 7.168956,
                6.207962, 3.876891, 4.597115, 4.533345,
                5.779965, 4.916631])

hr = load_heartrate(as_series=True)
wineind = load_wineind()
lynx = load_lynx()


def series_with_dt_index(n):
    """Helper fn to create a monotonic series with Datetime index"""
    time_column = []
    date = datetime.date(2022, 1, 1)

    for i in range(n):
        time_column.append(date + datetime.timedelta(days=i))

    return pd.Series(range(n), index=time_column)


def test_basic_arma():
    arma = ARIMA(order=(0, 0, 0), suppress_warnings=True)
    preds = arma.fit_predict(y)  # fit/predict for coverage

    # No OOB, so assert none
    assert arma.oob_preds_ is None

    # test some of the attrs
    assert_almost_equal(arma.aic(), 11.201, decimal=3)  # equivalent in R

    # intercept is param 0
    intercept = arma.params()[0]
    assert_almost_equal(intercept, 0.441, decimal=3)  # equivalent in R
    assert_almost_equal(arma.aicc(), 11.74676, decimal=5)
    assert_almost_equal(arma.bic(), 13.639060053303311, decimal=5)

    # get predictions
    expected_preds = np.array([0.44079876, 0.44079876, 0.44079876,
                               0.44079876, 0.44079876, 0.44079876,
                               0.44079876, 0.44079876, 0.44079876,
                               0.44079876])

    # generate predictions
    assert_array_almost_equal(preds, expected_preds)

    # Make sure we can get confidence intervals
    expected_intervals = np.array([
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139],
        [-0.10692387, 0.98852139]
    ])

    _, intervals = arma.predict(n_periods=10, return_conf_int=True,
                                alpha=0.05)
    assert_array_almost_equal(intervals, expected_intervals)


def test_issue_30():
    # From the issue:
    vec = np.array([33., 44., 58., 49., 46., 98., 97.])

    arm = AutoARIMA(out_of_sample_size=1, seasonal=False,
                    suppress_warnings=True)
    arm.fit(vec)

    # This is a way to force it:
    ARIMA(order=(0, 1, 0), out_of_sample_size=1).fit(vec)

    # Want to make sure it works with X arrays as well
    X = np.random.RandomState(1).rand(vec.shape[0], 2)
    auto_arima(vec, X=X, out_of_sample_size=1,
               seasonal=False,
               suppress_warnings=True)

    # This is a way to force it:
    ARIMA(order=(0, 1, 0), out_of_sample_size=1).fit(vec, X=X)


@pytest.mark.parametrize(
    # will be m - d
    'model', [
        ARIMA(order=(2, 0, 0)),  # arma
        ARIMA(order=(2, 1, 0)),  # arima
        ARIMA(order=(2, 1, 0), seasonal_order=(1, 0, 0, 12)),  # sarimax
    ]
)
def test_predict_in_sample_conf_int(model):
    model.fit(wineind)
    expected_m_dim = wineind.shape[0]
    preds, confints = model.predict_in_sample(return_conf_int=True, alpha=0.05)
    assert preds.shape[0] == expected_m_dim
    assert confints.shape == (expected_m_dim, 2)


@pytest.mark.parametrize(
    'y,model,start,end,exp_len',
    [
        pytest.param(
            series_with_dt_index(30),
            ARIMA(order=(0, 1, 0)),
            2,
            5,
            4,
        ),
        pytest.param(
            series_with_dt_index(30),
            ARIMA(order=(0, 1, 0)),
            "20220103",
            "20220106",
            4,
        ),
    ]
)
def test_predict_in_sample_non_int_index(y, model, start, end, exp_len):
    # issue 499
    model.fit(y)
    preds = model.predict_in_sample(start=start, end=end)
    assert preds.shape[0] == exp_len


@pytest.mark.parametrize(
    'model', [
        ARIMA(order=(2, 0, 0)),  # arma
        ARIMA(order=(2, 1, 0)),  # arima
        ARIMA(order=(2, 1, 0), seasonal_order=(1, 0, 0, 12)),  # sarimax
    ]
)
@pytest.mark.parametrize('X', [None, rs.rand(wineind.shape[0], 2)])
@pytest.mark.parametrize('confints', [True, False])
def test_predict_in_sample_X(model, X, confints):
    model.fit(wineind, X=X)
    res = model.predict_in_sample(X, return_conf_int=confints)
    if confints:
        assert isinstance(res, tuple) and len(res) == 2
    else:
        assert isinstance(res, np.ndarray)


def _two_times_mse(y_true, y_pred, **_):
    """A custom loss to test we can pass custom scoring metrics"""
    return mean_squared_error(y_true, y_pred) * 2


@pytest.mark.parametrize('as_pd', [True, False])
@pytest.mark.parametrize('scoring', ['mse', _two_times_mse])
def test_with_oob_and_X(as_pd, scoring):
    endog = hr
    X = np.random.RandomState(1).rand(hr.shape[0], 3)
    if as_pd:
        X = pd.DataFrame.from_records(X)
        endog = pd.Series(hr)

    arima = ARIMA(order=(2, 1, 2),
                  suppress_warnings=True,
                  scoring=scoring,
                  out_of_sample_size=10).fit(y=endog, X=X)

    # show we can get oob score and preds
    arima.oob()


def test_with_oob():
    # show we can fit with CV (kinda)
    arima = ARIMA(order=(2, 1, 2),
                  suppress_warnings=True,
                  scoring='mse',
                  out_of_sample_size=10).fit(y=hr)

    oob = arima.oob()
    assert not np.isnan(oob)  # show this works

    # Assert the predictions give the expected MAE/MSE
    oob_preds = arima.oob_preds_
    assert oob_preds.shape[0] == 10
    scoring = val.get_scoring_metric('mse')
    assert scoring(hr[-10:], oob_preds) == oob

    # show we can fit if ooss < 0 and oob will be nan
    arima = ARIMA(order=(2, 1, 2), suppress_warnings=True,
                  out_of_sample_size=-1).fit(y=hr)
    assert np.isnan(arima.oob())

    # This will raise since n_steps is not an int
    with pytest.raises(TypeError):
        arima.predict(n_periods="5")

    # But that we CAN forecast with an int...
    _ = arima.predict(n_periods=5)  # noqa: F841

    # Show we fail if cv > n_samples
    with pytest.raises(ValueError):
        ARIMA(order=(2, 1, 2), out_of_sample_size=1000).fit(hr)


# Test Issue #28 ----------------------------------------------------------
def test_oob_for_issue_28():
    # Continuation of above: can we do one with an X array, too?
    xreg = rs.rand(hr.shape[0], 4)
    arima = ARIMA(order=(2, 1, 2),
                  suppress_warnings=True,
                  out_of_sample_size=10).fit(
        y=hr, X=xreg)

    oob = arima.oob()
    assert not np.isnan(oob)

    # Assert that the endog shapes match. First is equal to the original,
    # and the second is the differenced array
    assert np.allclose(arima.arima_res_.data.endog, hr, rtol=1e-2)
    assert arima.arima_res_.model.endog.shape[0] == hr.shape[0]

    # Now assert the same for X
    assert np.allclose(arima.arima_res_.data.exog, xreg, rtol=1e-2)
    assert arima.arima_res_.model.exog.shape[0] == xreg.shape[0]

    # Compare the OOB score to an equivalent fit on data - 10 obs, but
    # without any OOB scoring, and we'll show that the OOB scoring in the
    # first IS in fact only applied to the first (train - n_out_of_bag)
    # samples
    arima_no_oob = ARIMA(
        order=(2, 1, 2), suppress_warnings=True,
        out_of_sample_size=0).fit(y=hr[:-10],
                                  X=xreg[:-10, :])

    scoring = val.get_scoring_metric(arima_no_oob.scoring)
    preds = arima_no_oob.predict(n_periods=10, X=xreg[-10:, :])
    assert np.allclose(oob, scoring(hr[-10:], preds), rtol=1e-2)

    # Show that the model parameters are not the same because the model was
    # updated.
    xreg_test = rs.rand(5, 4)
    assert not np.allclose(arima.params(), arima_no_oob.params(), rtol=1e-2)

    # Now assert on the forecast differences.
    with_oob_forecasts = arima.predict(n_periods=5, X=xreg_test)
    no_oob_forecasts = arima_no_oob.predict(n_periods=5,
                                            X=xreg_test)

    with pytest.raises(AssertionError):
        assert_array_almost_equal(with_oob_forecasts, no_oob_forecasts)

    # But after we update the no_oob model with the latest data, we should
    # be producing the same exact forecasts

    # First, show we'll fail if we try to add observations with no X
    with pytest.raises(ValueError):
        arima_no_oob.update(hr[-10:], None)

    # Also show we'll fail if we try to add mis-matched shapes of data
    with pytest.raises(ValueError):
        arima_no_oob.update(hr[-10:], xreg_test)

    # Show we fail if we try to add observations with a different dim X
    with pytest.raises(ValueError):
        arima_no_oob.update(hr[-10:], xreg_test[:, :2])

    # Actually add them now, and compare the forecasts (should be the same)
    arima_no_oob.update(hr[-10:], xreg[-10:, :])
    assert np.allclose(with_oob_forecasts,
                       arima_no_oob.predict(n_periods=5, X=xreg_test),
                       rtol=1e-2)


# Test the OOB functionality for SARIMAX (Issue #28) --------------------------

def test_oob_sarimax():
    xreg = rs.rand(wineind.shape[0], 2)
    fit = ARIMA(order=(1, 1, 1),
                seasonal_order=(0, 1, 1, 12),
                maxiter=5,
                out_of_sample_size=15).fit(y=wineind, X=xreg)

    fit_no_oob = ARIMA(order=(1, 1, 1),
                       seasonal_order=(0, 1, 1, 12),
                       out_of_sample_size=0,
                       maxiter=5,
                       suppress_warnings=True).fit(y=wineind[:-15],
                                                   X=xreg[:-15, :])

    # now assert some of the same things here that we did in the former test
    oob = fit.oob()

    # compare scores:
    scoring = val.get_scoring_metric(fit_no_oob.scoring)
    no_oob_preds = fit_no_oob.predict(n_periods=15, X=xreg[-15:, :])
    assert np.allclose(oob, scoring(wineind[-15:], no_oob_preds), rtol=1e-2)

    # show params are no longer the same
    assert not np.allclose(fit.params(), fit_no_oob.params(), rtol=1e-2)

    # show we can add the new samples and get the exact same forecasts
    xreg_test = rs.rand(5, 2)
    fit_no_oob.update(wineind[-15:], xreg[-15:, :])
    assert np.allclose(fit.predict(5, xreg_test),
                       fit_no_oob.predict(5, xreg_test),
                       rtol=1e-2)

    # And also the params should be close now after updating
    assert np.allclose(fit.params(), fit_no_oob.params())

    # Show we can get a confidence interval out here
    preds, conf = fit.predict(5, xreg_test, return_conf_int=True)
    assert all(isinstance(a, np.ndarray) for a in (preds, conf))


# Test Issue #29 (d=0, cv=True) -----------------------------------------------


class TestIssue29:
    dta = sm.datasets.sunspots.load_pandas().data
    dta.index = pd.Index(sm.tsa.datetools.dates_from_range('1700', '2008'))
    del dta["YEAR"]

    xreg = np.random.RandomState(1).rand(dta.shape[0], 3)

    @pytest.mark.parametrize('d', [0, 1])
    @pytest.mark.parametrize('cv', [0, 3])
    @pytest.mark.parametrize('X', [xreg, None])
    def test_oob_for_issue_29(self, d, cv, X):
        model = ARIMA(order=(2, d, 0),
                      out_of_sample_size=cv).fit(self.dta, X=X)

        # If X is defined, we need to pass n_periods of
        # X rows to the predict function. Otherwise we'll
        # just leave it at None
        if X is not None:
            xr = X[:3, :]
        else:
            xr = None

        _, _ = model.predict(n_periods=3, return_conf_int=True, X=xr)


def _try_get_attrs(arima):
    # show we can get all these attrs without getting an error
    attrs = {
        'aic', 'aicc', 'arparams', 'arroots', 'bic', 'bse', 'conf_int',
        'df_model', 'df_resid', 'hqic', 'maparams', 'maroots',
        'params', 'pvalues', 'resid', 'fittedvalues',
    }

    # this just shows all of these attrs work.
    for attr in attrs:
        getattr(arima, attr)()


def test_more_elaborate():
    # show we can fit this with a non-zero order
    arima = ARIMA(order=(2, 1, 2), suppress_warnings=True).fit(y=hr)
    _try_get_attrs(arima)

    # can we fit this same arima with a made-up X array?
    xreg = rs.rand(hr.shape[0], 4)
    arima = ARIMA(order=(2, 1, 2), suppress_warnings=True).fit(y=hr, X=xreg)
    _try_get_attrs(arima)

    with tempfile.TemporaryDirectory() as tdir:

        # pickle this for the __get/setattr__ coverage.
        # since the only time this is tested is in parallel in auto.py,
        # this doesn't actually get any coverage proof...
        fl = os.path.join(tdir, 'some_temp_file.pkl')
        with open(fl, 'wb') as p:
            pickle.dump(arima, p)

        # show we can predict with this even though it's been pickled
        new_xreg = rs.rand(5, 4)
        _preds = arima.predict(n_periods=5, X=new_xreg)

        # now unpickle
        with open(fl, 'rb') as p:
            other = pickle.load(p)

        # show we can still predict, compare
        _other_preds = other.predict(n_periods=5, X=new_xreg)
        assert_array_almost_equal(_preds, _other_preds)

    # now show that since we fit the ARIMA with an X array,
    # we need to provide one for predictions otherwise it breaks.
    with pytest.raises(ValueError):
        arima.predict(n_periods=5, X=None)

    # show that if we DO provide an X and it's the wrong dims, we
    # also break things down.
    with pytest.raises(ValueError):
        arima.predict(n_periods=5, X=rs.rand(4, 4))


def test_the_r_src():
    # this is the test the R code provides
    fit = ARIMA(order=(2, 0, 1), trend='c', suppress_warnings=True).fit(abc)

    # the R code's AIC = 135.4
    assert abs(135.4 - fit.aic()) < 1.0

    # the R code's AICc = ~ 137
    assert abs(137 - fit.aicc()) < 1.0

    # the R code's BIC = ~145
    assert abs(145 - fit.bic()) < 1.0

    # R's coefficients:
    #     ar1      ar2     ma1    mean
    # -0.6515  -0.2449  0.8012  5.0370

    arparams = fit.arparams()
    assert_almost_equal(arparams, [-0.6515, -0.2449], decimal=3)

    maparams = fit.maparams()
    assert_almost_equal(maparams, [0.8012], decimal=3)

    # > fit = forecast::auto.arima(abc, max.p=5, max.d=5,
    #             max.q=5, max.order=100, stepwise=F)
    fit = auto_arima(abc, max_p=5, max_d=5, max_q=5, max_order=100,
                     seasonal=False, trend='c', suppress_warnings=True,
                     error_action='ignore')

    assert abs(135.28 - fit.aic()) < 1.0  # R's is 135.28


def test_with_seasonality():
    fit = ARIMA(order=(1, 1, 1),
                seasonal_order=(0, 1, 1, 12),
                suppress_warnings=True).fit(y=wineind)
    _try_get_attrs(fit)

    # R code AIC result is ~3004
    assert abs(fit.aic() - 3004) < 100  # show equal within 100 or so

    # R code AICc result is ~3005
    assert abs(fit.aicc() - 3005) < 100  # show equal within 100 or so

    # R code BIC result is ~3017
    assert abs(fit.bic() - 3017) < 100  # show equal within 100 or so

    # show we can predict in-sample
    fit.predict_in_sample()

    # test with SARIMAX confidence intervals
    fit.predict(n_periods=10, return_conf_int=True, alpha=0.05)


# Test that (as of v0.9.1) we can pickle a model, pickle it again, load both
# and create predictions.
def test_double_pickle():
    arima = ARIMA(order=(0, 0, 0), trend='c', suppress_warnings=True)
    arima.fit(y)

    with tempfile.TemporaryDirectory() as tdir:

        # Now save it twice
        file_a = os.path.join(tdir, 'first.pkl')
        file_b = os.path.join(tdir, 'second.pkl')

        # No compression
        joblib.dump(arima, file_a)

        # Sleep between pickling so that the "pickle hash" for the ARIMA is
        # different by enough. We could theoretically also just use a UUID
        # for part of the hash to make sure it's unique?
        time.sleep(0.5)

        # Some compression
        joblib.dump(arima, file_b, compress=2)

        # Load both and prove they can both predict
        loaded_a = joblib.load(file_a)  # type: ARIMA
        loaded_b = joblib.load(file_b)  # type: ARIMA
        pred_a = loaded_a.predict(n_periods=5)
        pred_b = loaded_b.predict(n_periods=5)
        assert np.allclose(pred_a, pred_b)


# Regression testing for unpickling an ARIMA from an older version
def test_for_older_version():
    # Fit an ARIMA
    arima = ARIMA(order=(0, 0, 0), trend='c', suppress_warnings=True)

    # There are three possibilities here:
    # 1. The model is serialized/deserialized BEFORE it has been fit.
    #    This means we should not get a warning.
    #
    # 2. The model is saved after being fit, but it does not have a
    #    pkg_version_ attribute due to it being an old (very old) version.
    #    We still warn for this
    #
    # 3. The model is saved after the fit, and it's version does not match.
    #    We warn for this.
    for case, do_fit, expect_warning in [(1, False, False),
                                         (2, True, True),
                                         (3, True, True)]:

        # Only fit it if we should
        if do_fit:
            arima.fit(y)

        # If it's case 2, we remove the pkg_version_. If 3, we set it low
        if case == 2:
            delattr(arima, 'pkg_version_')
        elif case == 3:
            arima.pkg_version_ = '0.0.1'  # will always be < than current

        with tempfile.TemporaryDirectory() as tdir:

            pickle_file = os.path.join(tdir, 'model.pkl')
            joblib.dump(arima, pickle_file)

            # Now unpickle it and show that we get a warning (if expected)
            if expect_warning:
                with pytest.warns(UserWarning):
                    arm = joblib.load(pickle_file)  # type: ARIMA
            else:
                arm = joblib.load(pickle_file)  # type: ARIMA

            # we can still produce predictions (only if we fit)
            if do_fit:
                arm.predict(n_periods=4)


@pytest.mark.parametrize(
    'order,seasonal', [
        # ARMA
        pytest.param((1, 0, 0), (0, 0, 0, 0)),

        # ARIMA
        pytest.param((1, 1, 0), (0, 0, 0, 0)),

        # SARIMAX
        pytest.param((1, 1, 0), (1, 0, 0, 12))
    ])
def test_with_intercept(order, seasonal):
    n_params = None
    for intercept in (False, True):
        modl = ARIMA(order=order,
                     seasonal_order=seasonal,
                     with_intercept=intercept).fit(lynx)

        if not intercept:  # first time
            n_params = modl.params().shape[0]
        else:
            # With an intercept, should be 1 more
            assert modl.params().shape[0] == n_params + 1


def test_to_dict_returns_dict():
    train = lynx[:90]
    modl = auto_arima(train, start_p=1, start_q=1, start_P=1, start_Q=1,
                      max_p=5, max_q=5, max_P=5, max_Q=5, seasonal=True,
                      stepwise=True, suppress_warnings=True, D=10, max_D=10,
                      error_action='ignore')
    assert isinstance(modl.to_dict(), dict)


def test_to_dict_raises_attribute_error_on_unfit_model():
    modl = ARIMA(order=(1, 1, 0))
    with pytest.raises(AttributeError):
        modl.to_dict()


# tgsmith61591: I really hate this test. But it ensures no drift, at least..
def test_to_dict_is_accurate():
    train = lynx[:90]
    modl = auto_arima(train, start_p=1, start_q=1, start_P=1, start_Q=1,
                      max_p=5, max_q=5, max_P=5, max_Q=5, seasonal=True,
                      stepwise=True, suppress_warnings=True, D=10, max_D=10,
                      error_action='ignore')
    expected = {
        'pvalues': np.array([2.04752445e-03, 1.43710465e-61,
                             1.29504002e-10, 5.22119887e-15]),
        'resid': np.array(
            [-1244.3973072, -302.89697033, -317.63342593, -304.57267897,
             131.69413491, 956.15566697, 880.37459722, 2445.86460353,
             -192.84268876, -177.1932523, -101.67727903, 384.05487582,
             -304.52047818, -570.72748088, -497.48574217, 1286.86848903,
             -400.22840217, 1017.55518758, -1157.37024626, -295.26213543,
             104.79931827, -574.9867485, -588.49652697, -535.37707505,
             -355.71298419, -164.06179682, 574.51900799, 15.45522718,
             -1358.43416826, 120.42735893, -147.94038284, -685.64124874,
             -365.18947057, -243.79704985, 317.79437422, 585.59553667,
             34.70605783, -216.21587989, -692.53375089, 116.87379358,
             -385.52193301, -540.95554558, -283.16913167, 438.72324376,
             1078.63542578, 3198.50449405, -2167.76083646, -783.80525821,
             1384.85947061, -95.84379882, -728.85293118, -35.68476597,
             211.33538732, -379.91950618, 599.42290213, -839.30599392,
             -201.97018962, -393.28468589, -376.16010796, -516.52280993,
             -369.25037143, -362.25159504, 783.17714317, 207.96692746,
             1744.27617969, -1573.37293342, -479.20751405, 473.18948601,
             -503.20223823, -648.62384466, -671.12469446, -547.51554005,
             -501.37768686, 274.76714385, 2073.1897026, -1063.19580729,
             -1664.39957997, 882.73400004, -304.17429193, -422.60267409,
             -292.34984241, -27.76090888, 1724.60937822, 3095.90133612,
             -325.78549678, 110.95150845, 645.21273504, -135.91225092,
             417.12710097, -118.27553718]),
        'order': (2, 0, 0),
        'seasonal_order': (0, 0, 0, 0),
        'oob': np.nan,
        'aic': 1487.8850037609368,
        'aicc': 1488.3555919962284,
        'bic': 1497.8842424422578,
        'bse': np.array([2.26237893e+02, 6.97744631e-02,
                         9.58556537e-02, 1.03225425e+05]),
        'params': np.array([6.97548186e+02, 1.15522102e+00,
                            -6.16136459e-01, 8.07374077e+05])
    }

    actual = modl.to_dict()

    assert actual.keys() == expected.keys()
    assert_almost_equal(actual['pvalues'], expected['pvalues'], decimal=5)
    assert_allclose(actual['resid'], expected['resid'], rtol=1e-3)
    assert actual['order'] == expected['order']
    assert actual['seasonal_order'] == expected['seasonal_order']
    assert np.isnan(actual['oob'])
    assert_almost_equal(actual['aic'], expected['aic'], decimal=5)
    assert_almost_equal(actual['aicc'], expected['aicc'], decimal=5)
    assert_almost_equal(actual['bic'], expected['bic'], decimal=5)
    assert_allclose(actual['bse'], expected['bse'], rtol=1e-3)
    assert_almost_equal(actual['params'], expected['params'], decimal=3)


def test_serialization_methods_equal():
    arima = ARIMA(order=(0, 0, 0), suppress_warnings=True).fit(y)

    with tempfile.TemporaryDirectory() as dirname:
        joblib_path = os.path.join(dirname, "joblib.pkl")
        joblib.dump(arima, joblib_path)
        loaded = joblib.load(joblib_path)
        joblib_preds = loaded.predict()

        pickle_path = os.path.join(dirname, "pickle.pkl")
        with open(pickle_path, 'wb') as p:
            pickle.dump(arima, p)

        with open(pickle_path, 'rb') as p:
            loaded = pickle.load(p)
        pickle_preds = loaded.predict()

    assert_array_almost_equal(joblib_preds, pickle_preds)


@pytest.mark.parametrize(
    'model', [
        # ARMA
        ARIMA(order=(1, 0, 0)),

        # ARIMA
        ARIMA(order=(1, 1, 2)),

        # SARIMAX
        ARIMA(order=(1, 1, 2), seasonal_order=(0, 1, 1, 12))
    ]
)
def test_issue_104(model):
    # Issue 104 shows that observations were not being updated appropriately.
    # We need to make sure they update for ALL models (ARMA, ARIMA, SARIMAX)
    endog = wineind
    train, test = endog[:125], endog[125:]

    model.fit(train)
    preds1 = model.predict(n_periods=100)

    model.update(test)
    preds2 = model.predict(n_periods=100)

    # These should be DIFFERENT
    assert not np.array_equal(preds1, preds2)


def test_issue_286():
    mod = ARIMA(order=(1, 1, 2))
    mod.fit(wineind)

    with pytest.raises(ValueError) as ve:
        mod.predict_in_sample(start=0)
    assert "In-sample predictions undefined for" in pytest_error_str(ve)


@pytest.mark.parametrize(
    'model', [
        # ARMA
        ARIMA(order=(1, 0, 0)),

        # ARIMA
        ARIMA(order=(1, 1, 0))
    ]
)
def test_update_1_iter(model):
    # The model should *barely* change if we update with one iter.
    endog = wineind
    train, test = endog[:145], endog[145:]

    model.fit(train)
    params1 = model.params()

    # Now update with 1 iteration, and show params have not changed too much
    model.update(test, maxiter=1)
    params2 = model.params()

    # They should be close
    assert np.allclose(params1, params2, atol=0.05)


def test_ARMAtoMA():
    ar = np.array([0.5, 0.6])
    ma = np.array([0.4, 0.3, 0.1, 0.05])
    max_deg = 6
    equivalent_ma = ARMAtoMA(ar, ma, max_deg)
    ema_expected = np.array([0.9000, 1.3500, 1.3150, 1.5175, 1.5477, 1.6843])
    assert_array_almost_equal(equivalent_ma, ema_expected, decimal=4)
