"""
   Testing the FileCatalog logic
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

import sys
import unittest
import mock

import DIRAC
from DIRAC.Resources.Catalog.FileCatalog import FileCatalog

from DIRAC import S_OK, S_ERROR

__RCSID__ = "$Id $"

current_module = sys.modules[__name__]


class GenericCatalog(object):
    """Dummy catalog"""

    def __init__(self, name, nb_read, nb_read_no_lfn, nb_write, nb_write_no_lfn):

        self.w_method = []
        self.r_method = []
        self.no_lfn = []
        self.name = name

        self.__generateMethods("read", self.r_method, nb_read, nb_read_no_lfn)
        self.__generateMethods("write", self.w_method, nb_write, nb_write_no_lfn)

    def hasCatalogMethod(self, methName):
        return methName in self.w_method or methName in self.r_method

    def __generateMethods(self, mType, methodList, nb_method, nb_method_no_lfn):
        """Generates methods, read or write, and adds them to the appropriate list,
        including no_lfn if needed.
        The no_lfn methods are taken starting from the end.

        :param mType: read or write
        :param methodList: in which list to put the names
        :param nb_methods: number of methods
        :param nb_methods_no_lfn: number of these methods that should go in no lfn

        """

        no_lfn_start = nb_method - nb_method_no_lfn + 1
        for catId in range(1, nb_method + 1):
            mName = "%s%d" % (mType, catId)
            methodList.append(mName)
            if catId >= no_lfn_start:
                self.no_lfn.append(mName)

    def getInterfaceMethods(self):
        return self.r_method, self.w_method, self.no_lfn

    def generic(self, *args, **kwargs):
        """Returns a status depending on the input.
        For a normal read or write method, it looks for the catalog
        name in the LFN. If it is there, it looks at which status it is
        supposed to return: S_Error, or put the LFN in the Failed dict.
        """

        successful = {}
        failed = {}

        if self.call in self.no_lfn:
            if not args:
                return S_OK("yeah")
            ret = args[0]
            if self.name in ret:
                return S_ERROR("%s.%s did not go well" % (self.name, self.call))
            else:
                return S_OK("yeah")

        lfns = args[0]

        for lfn in lfns:
            lfnSplit = lfn.split("/")
            try:
                idName = lfnSplit.index(self.name)
                retType = lfnSplit[idName + 1]
                if retType == "Error":
                    return S_ERROR("%s.%s did not go well" % (self.name, self.call))
                elif retType == "Failed":
                    failed[lfn] = "%s.%s failed for %s" % (self.name, self.call, lfn)
            except ValueError:
                successful[lfn] = "yeah"

        return S_OK({"Successful": successful, "Failed": failed})

    def __getattr__(self, meth):
        self.call = meth
        return self.generic


def mock_fc_getSelectedCatalogs(self, desiredCatalogs):
    """Mock the getSelectedCatalogs method
    The name of the catalog should contain the following info, separated by '_':
      * the name of the catalog
      * True or False if it is a Master
      * True or False for Read
      * True or False for Write
      * nb of read op
      * nb of read no lfn
      * nb of write op
      * nb of write no lfn
    """

    for catalogDescription in desiredCatalogs:
        name, master, read, write, nb_read, nb_read_no_lfn, nb_write, nb_write_no_lfn = catalogDescription.split("_")
        master = eval(master)
        read = eval(read)
        write = eval(write)
        nb_read = eval(nb_read)
        nb_read_no_lfn = eval(nb_read_no_lfn)
        nb_write = eval(nb_write)
        nb_write_no_lfn = eval(nb_write_no_lfn)

        obj = GenericCatalog(name, nb_read, nb_read_no_lfn, nb_write, nb_write_no_lfn)

        if read:
            self.readCatalogs.append((name, obj, master))
        if write:
            self.writeCatalogs.append((name, obj, master))

    return S_OK()


def mock_fc_getEligibleCatalogs(self):
    """We return an object that always returns True
    if we ask whether an item is in it
    """

    class mockList(object):
        def __contains__(self, item):
            return True

    x = mockList()
    return S_OK(x)


def writeList(count, reverse=None):
    """If reverse is none, returns write1, ...., write<count>
    if reverse is set, returns a list with <count> elements backward from read<reverse>
    """
    if reverse:
        return ["write%s" % i for i in range(reverse, reverse - count, -1)]
    return ["write%s" % i for i in range(1, count + 1)]


def readList(count, reverse=None):
    """If reverse is none, returns read1, ...., read<count>
    if reverse is set, returns a list with <count> elements backward from read<reverse>
    """
    if reverse:
        return ["read%s" % i for i in range(reverse, reverse - count, -1)]

    return ["read%s" % i for i in range(1, count + 1)]


class TestInitialization(unittest.TestCase):
    """Tests the logic of the init mechanism"""

    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getSelectedCatalogs",
        side_effect=mock_fc_getSelectedCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getEligibleCatalogs",
        side_effect=mock_fc_getEligibleCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    def test_01_init(self, mk_getSelectedCatalogs, mk_getEligibleCatalogs):
        """Check logic of init"""

        # We should not be able to have 2 masters
        twoMastersFc = FileCatalog(catalogs=["c1_True_True_True_5_2_2_0", "c2_True_True_True_5_2_2_0"])
        self.assertTrue(not twoMastersFc.isOK())

        # One master should be ok
        oneMasterFc = FileCatalog(catalogs=["c1_True_True_True_2_0_2_2", "c2_False_True_True_3_1_4_2"])
        self.assertTrue(oneMasterFc.isOK())

        # With a master, the write method should be the method of the master
        self.assertEqual(sorted(oneMasterFc.write_methods), writeList(2))
        # The read methods and no_lfn should be from all catalogs
        self.assertEqual(sorted(oneMasterFc.ro_methods), readList(3))

        # The no_lfns methods are from everywhere
        # write1 and write2 from c1
        # write3, write4, read3 from c2
        self.assertEqual(sorted(oneMasterFc.no_lfn_methods), sorted(readList(1, reverse=3) + writeList(4)))

        # No master should be ok
        noMasterFc = FileCatalog(catalogs=["c1_False_True_True_2_0_2_0", "c2_False_True_True_3_0_4_0"])
        self.assertTrue(oneMasterFc.isOK())
        # With no master, the write method should be from all catalogs
        self.assertEqual(sorted(noMasterFc.write_methods), writeList(4))
        # The read methods and no_lfn should be from all catalogs
        self.assertEqual(sorted(noMasterFc.ro_methods), readList(3))


class TestWrite(unittest.TestCase):
    """Tests of the w_execute method"""

    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getSelectedCatalogs",
        side_effect=mock_fc_getSelectedCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getEligibleCatalogs",
        side_effect=mock_fc_getEligibleCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    def test_01_Normal(self, mk_getSelectedCatalogs, mk_getEligibleCatalogs):
        """Test behavior with one master and only standard write methods"""

        fc = FileCatalog(catalogs=["c1_True_True_True_2_0_2_0", "c2_False_True_True_3_0_1_0"])

        # Test a write method which is not in the master catalog
        with self.assertRaises(AttributeError):
            fc.write4("/lhcb/toto")

        # Test a write method which works for everybody
        lfn = "/lhcb/toto"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(lfn in res["Value"]["Successful"])
        self.assertEqual(sorted(["c1", "c2"]), sorted(res["Value"]["Successful"][lfn]))
        self.assertTrue(not res["Value"]["Failed"])

        # Test a write method that only the master has
        lfn = "/lhcb/toto"
        res = fc.write2(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(lfn in res["Value"]["Successful"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertTrue(not res["Value"]["Failed"])

        # Test a write method that makes an error for master
        # We should get an error
        lfn = "/lhcb/c1/Error"
        res = fc.write1(lfn)
        self.assertTrue(not res["OK"])

        # Test a write method that fails for master
        # The lfn should be in failed and only attempted for the master
        lfn = "/lhcb/c1/Failed"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(not res["Value"]["Successful"])
        self.assertEqual(["c1"], sorted(res["Value"]["Failed"][lfn]))

        # Test a write method that makes an error for non master
        # The lfn should be in failed for non master and successful for the master
        lfn = "/lhcb/c2/Error"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertEqual(["c2"], sorted(res["Value"]["Failed"][lfn]))

        # Test a write method that fails for non master
        # The lfn should be in failed for non master and successful for the master
        lfn = "/lhcb/c2/Failed"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertEqual(["c2"], sorted(res["Value"]["Failed"][lfn]))

    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getSelectedCatalogs",
        side_effect=mock_fc_getSelectedCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getEligibleCatalogs",
        side_effect=mock_fc_getEligibleCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    def test_02_condParser(self, mk_getSelectedCatalogs, mk_getEligibleCatalogs):
        """Test behavior of write methode when using FCConditionParser"""

        fc = FileCatalog(
            catalogs=["c1_True_True_True_2_0_2_0", "c2_False_True_True_3_0_1_0", "c3_False_True_True_3_0_1_0"]
        )

        # No condition for c3, so it should always pass
        fcConditions = {"c1": "Filename=find('c1_pass')", "c2": "Filename=find('c2_pass')"}

        # Everything pass everywhere
        lfn1 = "/lhcb/c1_pass/c2_pass/lfn1"
        lfn2 = "/lhcb/c1_pass/c2_pass/lfn2"
        res = fc.write1([lfn1, lfn2], fcConditions=fcConditions)
        self.assertTrue(res["OK"])
        self.assertEqual(sorted(res["Value"]["Successful"]), sorted([lfn1, lfn2]))
        self.assertEqual(sorted(res["Value"]["Successful"][lfn1]), sorted(["c1", "c2", "c3"]))
        self.assertEqual(sorted(res["Value"]["Successful"][lfn2]), sorted(["c1", "c2", "c3"]))
        self.assertTrue(not res["Value"]["Failed"])

        # Everything pass for the master, only lfn2 for c2
        lfn1 = "/lhcb/c1_pass/lfn1"
        lfn2 = "/lhcb/c1_pass/c2_pass/lfn2"
        res = fc.write1([lfn1, lfn2], fcConditions=fcConditions)
        self.assertTrue(res["OK"])
        self.assertEqual(sorted(res["Value"]["Successful"]), sorted([lfn1, lfn2]))
        self.assertEqual(sorted(res["Value"]["Successful"][lfn1]), ["c1", "c3"])
        self.assertEqual(sorted(res["Value"]["Successful"][lfn2]), sorted(["c1", "c2", "c3"]))
        self.assertTrue(not res["Value"]["Failed"])

        # One is not valid for the master, so we do nothing
        lfn1 = "/lhcb/c2_pass/lfn1"
        lfn2 = "/lhcb/c1_pass/c2_pass/lfn2"
        res = fc.write1([lfn1, lfn2], fcConditions=fcConditions)
        self.assertTrue(not res["OK"])

    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getSelectedCatalogs",
        side_effect=mock_fc_getSelectedCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getEligibleCatalogs",
        side_effect=mock_fc_getEligibleCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    def test_03_noLFN(self, mk_getSelectedCatalogs, mk_getEligibleCatalogs):
        """Test the no_lfn methods"""

        fc = FileCatalog(catalogs=["c1_True_True_True_2_0_2_1", "c2_False_True_True_3_0_2_1"])

        # all good
        res = fc.write2("/lhcb/toto")
        self.assertTrue(res["OK"])
        self.assertEqual(res["Value"], "yeah")

        # Fail in the master
        res = fc.write2("/lhcb/c1")
        self.assertTrue(not res["OK"])
        self.assertTrue("Value" not in res)

        # Fail in the non master
        res = fc.write2("/lhcb/c2")
        self.assertTrue(res["OK"])
        self.assertTrue("Value" in res)
        self.assertEqual(res["Value"], "yeah")


class TestRead(unittest.TestCase):
    """Tests of the w_execute method"""

    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getSelectedCatalogs",
        side_effect=mock_fc_getSelectedCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    @mock.patch.object(
        DIRAC.Resources.Catalog.FileCatalog.FileCatalog,
        "_getEligibleCatalogs",
        side_effect=mock_fc_getEligibleCatalogs,
        autospec=True,
    )  # autospec is for the binding of the method...
    def test_01_oneMasterNormal(self, mk_getSelectedCatalogs, mk_getEligibleCatalogs):
        """Test behavior with one master and only standard read methods"""

        fc = FileCatalog(catalogs=["c1_True_True_True_2_0_2_0", "c2_False_True_True_3_0_1_0"])

        # Test a write method which is not in the master catalog
        with self.assertRaises(AttributeError):
            fc.write4("/lhcb/toto")

        # Test a write method which works for everybody
        lfn = "/lhcb/toto"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(lfn in res["Value"]["Successful"])
        self.assertEqual(sorted(["c1", "c2"]), sorted(res["Value"]["Successful"][lfn]))
        self.assertTrue(not res["Value"]["Failed"])

        # Test a write method that only the master has
        lfn = "/lhcb/toto"
        res = fc.write2(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(lfn in res["Value"]["Successful"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertTrue(not res["Value"]["Failed"])

        # Test a write method that makes an error for master
        # We should get an error
        lfn = "/lhcb/c1/Error"
        res = fc.write1(lfn)
        self.assertTrue(not res["OK"])

        # Test a write method that fails for master
        # The lfn should be in failed and only attempted for the master
        lfn = "/lhcb/c1/Failed"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertTrue(not res["Value"]["Successful"])
        self.assertEqual(["c1"], sorted(res["Value"]["Failed"][lfn]))

        # Test a write method that makes an error for non master
        # The lfn should be in failed for non master and successful for the master
        lfn = "/lhcb/c2/Error"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertEqual(["c2"], sorted(res["Value"]["Failed"][lfn]))

        # Test a write method that fails for non master
        # The lfn should be in failed for non master and successful for the master
        lfn = "/lhcb/c2/Failed"
        res = fc.write1(lfn)
        self.assertTrue(res["OK"])
        self.assertEqual(["c1"], sorted(res["Value"]["Successful"][lfn]))
        self.assertEqual(["c2"], sorted(res["Value"]["Failed"][lfn]))


if __name__ == "__main__":
    suite = unittest.defaultTestLoader.loadTestsFromTestCase(TestInitialization)
    suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestWrite))
    suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(TestRead))

    unittest.TextTestRunner(verbosity=2).run(suite)
