#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""Tests for `data2passcal` package."""

from __future__ import division, print_function

import ftplib
import os
import sys
import unittest
import data2passcal.data2passcal as d2p

if sys.version_info < (3, 3):
    from mock import patch
else:
    from unittest.mock import patch

VERSION = '2024.2.0.1'

SEND4REAL = os.environ.get('SEND4REAL', 'False')
print("SEND4REAL=False by default. If one wants to test sending data to "
      "PASSCAL for 'real', set SEND4REAL=True as environment variable. "
      "ex: SEND4REAL=True python -m unittest test_data2passcal")

TEST_DIR = os.path.dirname(os.path.realpath(__file__)) + '/test_data'
MS_FILELIST = ['ST00.AB..BHZ.2007.160', 'ST00.AB..BHZ.2007.161',
               'ST00.AB..BHZ.2007.162', 'ST00.AB..BHZ.2007.163',
               'ST00.AB..BHZ.2007.164']

d2p.FTP_TIMEOUT = 1
d2p.FTP_RECONNECT_WAIT = 0.01
d2p.FTP_CONNECT_ATTEMPTS = 2
d2p.FTP_SEND_ATTEMPTS = 2


class TestData2passcal(unittest.TestCase):
    """Tests for `data2passcal` package."""

    def test_scan_dir(self):
        """
        Test basic functionality of scan_dir function
        """
        filelist = [os.path.join(TEST_DIR, f) for f in MS_FILELIST]
        if sys.version_info < (3, 2):
            self.assertItemsEqual(filelist, d2p.scan_dir(TEST_DIR),
                                  'scan_dir did not find the correct file(s)')
        else:
            self.assertCountEqual(filelist, d2p.scan_dir(TEST_DIR),
                                  'scan_dir did not find the correct file(s)')

    def test_ismseed(self):
        """Test basic functionality of ismseed function"""
        filelist = [os.path.join(TEST_DIR, f) for f in MS_FILELIST]
        for f in filelist:
            self.assertTrue(d2p.ismseed(f), '{} is not a miniseed file'
                            .format(os.path.basename(f)))

    @patch('data2passcal.data2passcal.ftplib.FTP', autospec=True)
    def test_get_FTP_mock(self, mock_ftp_constructor):
        """Mock test creating ftp connection to PASSCAL"""
        mock_ftp = mock_ftp_constructor.return_value
        d2p.get_FTP()
        self.assertLess(mock_ftp_constructor.call_count,
                        d2p.FTP_CONNECT_ATTEMPTS,
                        'Number of ftp connection attempts exceeeds {}'
                        .format(d2p.FTP_CONNECT_ATTEMPTS))
        mock_ftp.quit()

    @patch('data2passcal.data2passcal.urlopen', autospec=True)
    @patch('data2passcal.data2passcal.ftplib.FTP', autospec=True)
    def test_get_FTP_failure_mock(self, mock_ftp_constructor, mock_urlopen):
        """
        Mock test failure to create ftp connection to PASSCAL and exercise
        get_FTP()
        """
        mock_ftp_constructor.return_value = ftplib.error_temp
        d2p.get_FTP()
        self.assertGreater(mock_ftp_constructor.call_count, 1,
                           "ftplib.FTP() called only once - get_FTP() not "
                           "fully exercised!")

    @patch('data2passcal.data2passcal.ftplib.FTP', autospec=True)
    def test_send_data_mock(self, mock_ftp_constructor):
        """Mock test sending MSEED files (test data) to PASSCAL's QC system"""
        mock_ftp = mock_ftp_constructor.return_value
        filelist = [os.path.join(TEST_DIR, f) for f in MS_FILELIST[0:2]]
        d2p.send2passcal(filelist)
        self.assertTrue(mock_ftp.storbinary.called, 'No data sent')
        self.assertEqual(mock_ftp.storbinary.call_count, len(filelist),
                         'Failed to send all files - Sent {0} of {1}'
                         .format(mock_ftp.storbinary.call_count,
                                 len(filelist)))
        files_sent = []
        for x in mock_ftp.storbinary.call_args_list:
            args, kwargs = x
            files_sent.append(args[0].split(' ')[1])
        for f in MS_FILELIST[0:2]:
            self.assertLess(files_sent.count(f), d2p.FTP_SEND_ATTEMPTS,
                            'Attempted to send file {0} more than {1} times'
                            .format(f, d2p.FTP_SEND_ATTEMPTS))

    @patch('data2passcal.data2passcal.os._exit', autospec=True)
    @patch('data2passcal.data2passcal.urlopen', autospec=True)
    @patch('data2passcal.data2passcal.ftplib.FTP', autospec=True)
    def test_send_data_failure_mock(self, mock_ftp_constructor,
                                    mock_urlopen, mock_exit):
        """
        Mock test failure to create ftp connection to PASSCAL and exercise
        send_data()
        """
        mock_ftp_constructor.return_value = ftplib.error_temp
        filelist = [os.path.join(TEST_DIR, f) for f in MS_FILELIST[0:2]]
        d2p.send2passcal(filelist)
        self.assertTrue(mock_exit.called, "os._exit(1) never called - "
                                          "send_data() not fully exercised!")

    @unittest.skipIf(SEND4REAL == 'False', "skipping real send2passcal test")
    def test_send_data(self):
        """Test sending MSEED files (test data) to PASSCAL's QC system"""
        ftp = d2p.get_FTP()
        filelist = [os.path.join(TEST_DIR, f) for f in MS_FILELIST[0:2]]
        d2p.send2passcal(filelist)
        wdir = ftp.pwd()
        try:
            files_sent = [os.path.basename(x) for x in ftp.nlst(wdir)]
        except ftplib.error_perm() as resp:
            if str(resp) == "550 No files found":
                print("No files found in this directory")
            else:
                raise
        for f in MS_FILELIST[0:2]:
            self.assertIn(f, files_sent, 'File {} was not sent to PASSCAL'
                          .format(f))
        ftp.quit()


if __name__ == '__main__':
    unittest.main()