""" :mod: RequestTests
    =======================

    .. module: RequestTests
    :synopsis: test cases for Request class
    .. moduleauthor:: Krzysztof.Ciba@NOSPAMgmail.com

    test cases for Request class
"""

from __future__ import print_function
from __future__ import absolute_import
from __future__ import division
__RCSID__ = "$Id$"

import datetime

import six

from DIRAC.RequestManagementSystem.Client.Operation import Operation
from DIRAC.RequestManagementSystem.Client.File import File
from DIRAC.RequestManagementSystem.Client.Request import Request
from DIRAC.RequestManagementSystem.Client.ReqClient import printRequest


def optimizeRequest(req, printOutput=None):
  from DIRAC import gLogger
  if printOutput:
    if isinstance(printOutput, six.string_types):
      gLogger.always('Request %s:' % printOutput)
    printRequest(req)
    gLogger.always('=========== Optimized ===============')
  res = req.optimize()
  if printOutput:
    printRequest(req)
    gLogger.always('')
  return res


def createRequest(reqType):
  r = Request()

  # Simple failover
  op1 = Operation()
  f = File()
  f.LFN = '/This/is/an/LFN'
  op1.addFile(f)
  op1.Type = 'ReplicateAndRegister'
  op1.SourceSE = 'CERN-FAILOVER'
  op1.TargetSE = 'CERN-BUFFER'
  r.addOperation(op1)

  # You cannot reuse the same File object,
  # since it is a different entry in the DB
  fr = File()
  fr.LFN = '/This/is/an/LFN'
  op2 = Operation()
  op2.addFile(fr)
  op2.Type = 'RemoveReplica'
  op2.TargetSE = 'CERN-FAILOVER'
  r.addOperation(op2)
  if reqType == 0:
    return r

  # two files for Failover
  f1 = File()
  f1.LFN = '/This/is/a/second/LFN'
  op3 = Operation()
  op3.addFile(f1)
  op3.Type = 'ReplicateAndRegister'
  op3.SourceSE = 'CERN-FAILOVER'
  op3.TargetSE = 'CERN-BUFFER'
  r.addOperation(op3)

  f1r = File()
  f1r.LFN = '/This/is/a/second/LFN'
  op3 = Operation()
  op3.addFile(f1r)
  op3.Type = 'RemoveReplica'
  op3.TargetSE = 'CERN-FAILOVER'
  r.addOperation(op3)
  if reqType == 1:
    return r

  op = Operation()
  op.Type = 'ForwardDiset'
  if reqType == 2:
    r.addOperation(op)
    return r

  r.insertBefore(op, r[0])
  if reqType == 3:
    return r

  op4 = Operation()
  op4.Type = 'ForwardDiset'
  r.addOperation(op4)
  if reqType == 4:
    return r

  # 2 different FAILOVER SEs: removal not optimized
  r[1].SourceSE = 'RAL-FAILOVER'
  r[2].SourceSE = 'RAL-FAILOVER'
  if reqType == 5:
    return r

  # 2 different destinations, same FAILOVER: replication not optimized
  r[3].SourceSE = 'RAL-FAILOVER'
  r[4].SourceSE = 'RAL-FAILOVER'
  r[3].TargetSE = 'RAL-BUFFER'
  if reqType == 6:
    return r

  print('This should not happen, reqType =', reqType)

########################################################################


def test_CtorSerilization():
  """ c'tor and serialization """
  req = Request()
  assert isinstance(req, Request)
  assert req.JobID == 0
  assert req.Status == "Waiting"

  req = Request({"RequestName": "test", "JobID": 12345})
  assert isinstance(req, Request)
  assert req.RequestName == "test"
  assert req.JobID == 12345
  assert req.Status == "Waiting"

  req.SourceComponent = "test component"
  assert req.SourceComponent == b"test component"

  toJSON = req.toJSON()
  assert toJSON["OK"], "JSON serialization failed"

  fromJSON = toJSON["Value"]
  req = Request(fromJSON)


def test_Props():
  """ props """
  # # valid values
  req = Request()

  req.RequestID = 1
  assert req.RequestID == 1

  req.RequestName = "test"
  assert req.RequestName == "test"

  req.JobID = 1
  assert req.JobID == 1

  req.CreationTime = "1970-01-01 00:00:00"
  assert req.CreationTime == datetime.datetime(1970, 1, 1, 0, 0, 0)
  req.CreationTime = datetime.datetime(1970, 1, 1, 0, 0, 0)
  assert req.CreationTime == datetime.datetime(1970, 1, 1, 0, 0, 0)

  req.SubmitTime = "1970-01-01 00:00:00"
  assert req.SubmitTime == datetime.datetime(1970, 1, 1, 0, 0, 0)
  req.SubmitTime = datetime.datetime(1970, 1, 1, 0, 0, 0)
  assert req.SubmitTime == datetime.datetime(1970, 1, 1, 0, 0, 0)

  req.LastUpdate = "1970-01-01 00:00:00"
  assert req.LastUpdate == datetime.datetime(1970, 1, 1, 0, 0, 0)
  req.LastUpdate = datetime.datetime(1970, 1, 1, 0, 0, 0)
  assert req.LastUpdate == datetime.datetime(1970, 1, 1, 0, 0, 0)

  req.Error = ""


def test_Operations():
  """ operations arithmetic and state machine """
  req = Request()
  assert len(req) == 0

  transfer = Operation()
  transfer.Type = "ReplicateAndRegister"
  transfer.addFile(File({"LFN": "/a/b/c", "Status": "Waiting"}))

  getWaiting = req.getWaiting()
  assert getWaiting["OK"]
  assert getWaiting["Value"] is None

  req.addOperation(transfer)
  assert len(req) == 1
  assert transfer.Order == req.Order
  assert transfer.Status == "Waiting"

  getWaiting = req.getWaiting()
  assert getWaiting["OK"]
  assert getWaiting["Value"] == transfer

  removal = Operation({"Type": "RemoveFile"})
  removal.addFile(File({"LFN": "/a/b/c", "Status": "Waiting"}))

  req.insertBefore(removal, transfer)

  getWaiting = req.getWaiting()
  assert getWaiting["OK"]
  assert getWaiting["Value"] == removal

  assert len(req) == 2
  assert [op.Status for op in req] == ["Waiting", "Queued"]
  assert req.subStatusList() == ["Waiting", "Queued"]

  assert removal.Order == 0
  assert removal.Order == req.Order

  assert transfer.Order == 1

  assert removal.Status == "Waiting"
  assert transfer.Status == "Queued"

  for subFile in removal:
    subFile.Status = "Done"
  removal.Status = "Done"

  assert removal.Status == "Done"

  assert transfer.Status == "Waiting"
  assert transfer.Order == req.Order

  # len, looping
  assert len(req) == 2
  assert [op.Status for op in req] == ["Done", "Waiting"]
  assert req.subStatusList() == ["Done", "Waiting"]

  digest = req.toJSON()
  assert digest["OK"]

  getWaiting = req.getWaiting()
  assert getWaiting["OK"]
  assert getWaiting["Value"] == transfer


def test_FTS():
  """ FTS state machine """

  req = Request()
  req.RequestName = "FTSTest"

  ftsTransfer = Operation()
  ftsTransfer.Type = "ReplicateAndRegister"
  ftsTransfer.TargetSE = "CERN-USER"

  ftsFile = File()
  ftsFile.LFN = "/a/b/c"
  ftsFile.Checksum = "123456"
  ftsFile.ChecksumType = "Adler32"

  ftsTransfer.addFile(ftsFile)
  req.addOperation(ftsTransfer)

  assert req.Status == "Waiting", "1. wrong request status: %s" % req.Status
  assert ftsTransfer.Status == "Waiting", "1. wrong ftsStatus status: %s" % ftsTransfer.Status

  # # scheduled
  ftsFile.Status = "Scheduled"

  assert ftsTransfer.Status == "Scheduled", "2. wrong status for ftsTransfer: %s" % ftsTransfer.Status
  assert req.Status == "Scheduled", "2. wrong status for request: %s" % req.Status

  # # add new operation before FTS
  insertBefore = Operation()
  insertBefore.Type = "RegisterReplica"
  insertBefore.TargetSE = "CERN-USER"
  insertFile = File()
  insertFile.LFN = "/a/b/c"
  insertFile.PFN = "http://foo/bar"
  insertBefore.addFile(insertFile)
  req.insertBefore(insertBefore, ftsTransfer)

  assert insertBefore.Status == "Waiting", "3. wrong status for insertBefore: %s" % insertBefore.Status
  assert ftsTransfer.Status == "Scheduled", "3. wrong status for ftsStatus: %s" % ftsTransfer.Status
  assert req.Status == "Waiting", "3. wrong status for request: %s" % req.Status

  # # prev done
  insertFile.Status = "Done"

  assert insertBefore.Status == "Done", "4. wrong status for insertBefore: %s" % insertBefore.Status
  assert ftsTransfer.Status == "Scheduled", "4. wrong status for ftsStatus: %s" % ftsTransfer.Status
  assert req.Status == "Scheduled", "4. wrong status for request: %s" % req.Status

  # # reschedule
  ftsFile.Status = "Waiting"

  assert insertBefore.Status == "Done", "5. wrong status for insertBefore: %s" % insertBefore.Status
  assert ftsTransfer.Status == "Waiting", "5. wrong status for ftsStatus: %s" % ftsTransfer.Status
  assert req.Status == "Waiting", "5. wrong status for request: %s" % req.Status

  # # fts done
  ftsFile.Status = "Done"

  assert insertBefore.Status == "Done", "5. wrong status for insertBefore: %s" % insertBefore.Status
  assert ftsTransfer.Status == "Done", "5. wrong status for ftsStatus: %s" % ftsTransfer.Status
  assert req.Status == "Done", "5. wrong status for request: %s" % req.Status


def test_StateMachine():
  """ state machine tests """
  r = Request({"RequestName": "SMT"})
  assert r.Status == "Waiting", "1. wrong status %s" % r.Status

  r.addOperation(Operation({"Status": "Queued"}))
  assert r.Status == "Waiting", "2. wrong status %s" % r.Status

  r.addOperation(Operation({"Status": "Queued"}))
  assert r.Status == "Waiting", "3. wrong status %s" % r.Status

  r[0].Status = "Done"
  assert r.Status == "Waiting", "4. wrong status %s" % r.Status

  r[1].Status = "Done"
  assert r.Status == "Done", "5. wrong status %s" % r.Status

  r[0].Status = "Failed"
  assert r.Status == "Failed", "6. wrong status %s" % r.Status

  r[0].Status = "Queued"
  assert r.Status == "Waiting", "7. wrong status %s" % r.Status

  r.insertBefore(Operation({"Status": "Queued"}), r[0])
  assert r.Status == "Waiting", "8. wrong status %s" % r.Status

  r.insertBefore(Operation({"Status": "Queued"}), r[0])
  assert r.Status == "Waiting", "9. wrong status %s" % r.Status

  r.insertBefore(Operation({"Status": "Scheduled"}), r[0])
  assert r.Status == "Scheduled", "10. wrong status %s" % r.Status

  r.insertBefore(Operation({"Status": "Queued"}), r[0])
  assert r.Status == "Waiting", "11. wrong status %s" % r.Status

  r[0].Status = "Failed"
  assert r.Status == "Failed", "12. wrong status %s" % r.Status

  r[0].Status = "Done"
  assert r.Status == "Scheduled", "13. wrong status %s" % r.Status

  r[1].Status = "Failed"
  assert r.Status == "Failed", "14. wrong status %s" % r.Status

  r[1].Status = "Done"
  assert r.Status == "Waiting", "15. wrong status %s" % r.Status

  r[2].Status = "Scheduled"
  assert r.Status == "Scheduled", "16. wrong status %s" % r.Status

  r[2].Status = "Queued"
  assert r.Status == "Waiting", "17. wrong status %s" % r.Status

  r[2].Status = "Scheduled"
  assert r.Status == "Scheduled", "18. wrong status %s" % r.Status

  r = Request()
  for _ in range(5):
    r.addOperation(Operation({"Status": "Queued"}))

  r[0].Status = "Done"
  assert r.Status == "Waiting", "19. wrong status %s" % r.Status

  r[1].Status = "Done"
  assert r.Status == "Waiting", "20. wrong status %s" % r.Status

  r[2].Status = "Scheduled"
  assert r.Status == "Scheduled", "21. wrong status %s" % r.Status

  r[2].Status = "Done"
  assert r.Status == "Waiting", "22. wrong status %s" % r.Status


def test_List():
  """ setitem, delitem, getitem and dirty """

  r = Request()

  ops = [Operation() for i in range(5)]

  for op in ops:
    r.addOperation(op)

  for i, op in enumerate(ops):
    assert op == r[i], "__getitem__ failed"

  op = Operation()
  r[0] = op
  assert op == r[0], "__setitem__ failed"

  del r[0]
  assert len(r) == 4, "__delitem__ failed"


def test_Optimize():
  title = {
      0: 'Simple Failover',
      1: 'Double Failover',
      2: 'Double Failover + ForwardDiset',
      3: 'ForwardDiset + Double Failover',
      4: 'ForwardDiset + Double Failover + ForwardDiset',
      5: 'ForwardDiset + Double Failover (# Failover SE) + ForwardDiset',
      6: 'ForwardDiset + Double Failover (# Destination SE) + ForwardDiset'
  }
  debug = False
  if debug:
    print('')
  for reqType in title:
    r = createRequest(reqType)
    res = optimizeRequest(r, printOutput=title[reqType] if (debug and debug == reqType) else False)
    assert res['OK']
    assert res['Value']
    if reqType in (0, 1):
      assert len(r) == 2, 'Wrong number of operations: %d' % len(r)
      assert r[0].Type == 'ReplicateAndRegister'
      assert r[1].Type == 'RemoveReplica'
    if reqType == 1:
      assert len(r[0]) == 2, 'Wrong number of files: %d' % len(r[0])
      assert len(r[1]) == 2, 'Wrong number of files: %d' % len(r[1])
    elif reqType == 2:
      assert len(r) == 3, 'Wrong number of operations: %d' % len(r)
      assert r[0].Type == 'ReplicateAndRegister'
      assert r[1].Type == 'RemoveReplica'
      assert r[2].Type == 'ForwardDiset'
      assert len(r[0]) == 2, 'Wrong number of files: %d' % len(r[0])
      assert len(r[1]) == 2, 'Wrong number of files: %d' % len(r[1])
    elif reqType == 3:
      assert len(r) == 3, 'Wrong number of operations: %d' % len(r)
      assert r[1].Type == 'ReplicateAndRegister'
      assert r[2].Type == 'RemoveReplica'
      assert r[0].Type == 'ForwardDiset'
      assert len(r[1]) == 2, 'Wrong number of files: %d' % len(r[0])
      assert len(r[2]) == 2, 'Wrong number of files: %d' % len(r[1])
    elif reqType == 4:
      assert len(r) == 4, 'Wrong number of operations: %d' % len(r)
      assert r[1].Type == 'ReplicateAndRegister'
      assert r[2].Type == 'RemoveReplica'
      assert r[0].Type == 'ForwardDiset'
      assert r[3].Type == 'ForwardDiset'
      assert len(r[1]) == 2, 'Wrong number of files: %d' % len(r[0])
      assert len(r[2]) == 2, 'Wrong number of files: %d' % len(r[1])
    elif reqType == 5:
      assert len(r) == 5, 'Wrong number of operations: %d' % len(r)
      assert r[1].Type == 'ReplicateAndRegister'
      assert r[2].Type == 'RemoveReplica'
      assert r[3].Type == 'RemoveReplica'
      assert r[0].Type == 'ForwardDiset'
      assert r[4].Type == 'ForwardDiset'
      assert len(r[1]) == 2, 'Wrong number of files: %d' % len(r[0])
      assert len(r[2]) == 1, 'Wrong number of files: %d' % len(r[1])
      assert len(r[3]) == 1, 'Wrong number of files: %d' % len(r[1])
    elif reqType == 6:
      assert len(r) == 5, 'Wrong number of operations: %d' % len(r)
      assert r[1].Type == 'ReplicateAndRegister'
      assert r[2].Type == 'ReplicateAndRegister'
      assert r[3].Type == 'RemoveReplica'
      assert r[0].Type == 'ForwardDiset'
      assert r[4].Type == 'ForwardDiset'
      assert len(r[1]) == 1, 'Wrong number of files: %d' % len(r[0])
      assert len(r[2]) == 1, 'Wrong number of files: %d' % len(r[1])
      assert len(r[3]) == 2, 'Wrong number of files: %d' % len(r[1])
