{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}

module Eclogues.Scheduling.NomadTypesSpec where

import Eclogues.Scheduling.JsonTools (wrap, unwrap)
import Eclogues.Scheduling.NomadTypes
import Eclogues.Scheduling.Run (containerDownloadURI)
import TestUtils (echoContainer, exampleBase, forceName)

import Data.Aeson (eitherDecode', encode)
import Data.HashMap.Strict (fromList)
import Data.String.QQ (s)
import Eclogues.Job (mkSpec)
import qualified Eclogues.Job as Job

import Test.Hspec

{-# ANN spec ("HLint: ignore Use ." :: String) #-}

spec :: Spec
spec = do
    describe "NomadSubmission" $ do
        it "round-trips through Aeson" $
            eitherDecode' (encode nomad) `shouldBe` Right nomad
        it "looks right" $
            encode nomad `shouldBe` [s|{"Job":{"Datacenters":["dc1"],"Name":"uuid","ID":"uuid","Type":"batch","TaskGroups":[{"RestartPolicy":{"Mode":"fail","Attempts":0,"Interval":31536000000000000,"Delay":0},"EphemeralDisk":{"SizeMB":40},"Count":1,"Tasks":[{"Config":{"image":"echo:virgil","args":["hello world"],"load":"image"},"Artifacts":[{"RelativeDest":"local/image","GetterSource":"http://example.com/containers/echo","GetterMode":"file","GetterOptions":{"archive":""}}],"Env":{"JOB_ID":"example","ECLOGUES_BASE_URL":"http://example.com","DEPENDENCIES_SUCCEEDED":"","DEPENDENCIES_FAILED":""},"Driver":"docker","Resources":{"CPU":100,"MemoryMB":128},"Name":"uuid","LogConfig":{"MaxFileSizeMB":10,"MaxFiles":1}}],"Name":"uuid"}]}}|]
    describe "toNomad" $ do
        it "produces the expected result" $ do
            let uuid = forceName n
                exampleName = forceName exampleJobName
                containerName = forceName "echo"
                job = mkSpec exampleName (Job.ContainerId containerName) ["hello world"] minBound False mempty
            toNomad exampleBase (containerDownloadURI exampleBase) job uuid [] [] 1 `shouldBe` nomad
        it "formats dependencies information correctly" $ do
            let job1 = forceName "job1"
                job2 = forceName "job2"
                job3 = forceName "job3"
                uuid = forceName "uuid"
                containerName = forceName "echo"

            -- toNomad doesn't use jobSpec's `dependsOn` value, so we can be lazy and use `mempty`
            let jobSpec = mkSpec (forceName "job") (Job.ContainerId containerName) ["hello world"] minBound False mempty
                nom = toNomad exampleBase (containerDownloadURI exampleBase) jobSpec uuid [job1] [job2, job3] 1
            case unwrap nom of
                (NomadJob _ _ [TaskGroup _ [Task _ _ _ _ (Env _ _ failedDeps succeededDeps) _ _] _ _ _] _) ->
                    (failedDeps, succeededDeps) `shouldBe` ("job1", "job2,job3")
                _ -> error "malformed result"
    describe "JobSummary" $
        it "can be decoded" $ do
            let json = [s|{
                    "ID": "example",
                    "ParentID": "",
                    "Name": "example",
                    "Type": "service",
                    "Priority": 50,
                    "Status": "dead",
                    "StatusDescription": "",
                    "JobSummary": {
                      "JobID": "example",
                      "Summary": {
                        "cache": {
                          "Queued": 3,
                          "Complete": 2,
                          "Failed": 1,
                          "Running": 0,
                          "Starting": 0,
                          "Lost": 0
                        }
                      },
                      "Children": {
                        "Pending": 0,
                        "Running": 0,
                        "Dead": 0
                      },
                      "CreateIndex": 52,
                      "ModifyIndex": 96
                    },
                    "CreateIndex": 52,
                    "ModifyIndex": 93,
                    "JobModifyIndex": 52
                  }|]
            eitherDecode' json `shouldBe` Right (JobSummary "example" Dead (wrap $ fromList [("cache", 1)]))
    describe "AllocationInfo" $
        it "can be decoded" $ do
            let json = [s|{
                  "ID": "48bdab69-70ac-d8cd-7be9-dbea8b451393",
                  "EvalID": "ada1c0e6-d314-b627-9a88-bceaa8802a3a",
                  "Name": "0da2d1ce-b058-4898-b1c6-a5372aa9b267.0da2d1ce-b058-4898-b1c6-a5372aa9b267[0]",
                  "NodeID": "7b101213-2483-a1fa-fd5b-dec2c6a72537",
                  "JobID": "0da2d1ce-b058-4898-b1c6-a5372aa9b267",
                  "JobVersion": 0,
                  "TaskGroup": "0da2d1ce-b058-4898-b1c6-a5372aa9b267",
                  "DesiredStatus": "run",
                  "DesiredDescription": "",
                  "ClientStatus": "failed",
                  "ClientDescription": "",
                  "TaskStates": {
                    "0da2d1ce-b058-4898-b1c6-a5372aa9b267": {
                      "State": "dead",
                      "Failed": true,
                      "Restarts": 0,
                      "LastRestart": "0001-01-01T00:00:00Z",
                      "StartedAt": "2018-04-17T04:01:56.21256554Z",
                      "FinishedAt": "2018-04-17T04:01:56.299312219Z",
                      "Events": [
                        {
                          "Type": "Terminated",
                          "Time": 1523937716287903000,
                          "Message": "Docker container exited with non-zero exit code: 127",
                          "DisplayMessage": "Exit Code: 127, Exit Message: \"Docker container exited with non-zero exit code: 127\"",
                          "Details": {
                            "exit_code": "127",
                            "signal": "0",
                            "exit_message": "Docker container exited with non-zero exit code: 127"
                          },
                          "FailsTask": false,
                          "RestartReason": "",
                          "SetupError": "",
                          "DriverError": "",
                          "ExitCode": 127,
                          "Signal": 0,
                          "KillTimeout": 0,
                          "KillError": "",
                          "KillReason": "",
                          "StartDelay": 0,
                          "DownloadError": "",
                          "ValidationError": "",
                          "DiskLimit": 0,
                          "FailedSibling": "",
                          "VaultError": "",
                          "TaskSignalReason": "",
                          "TaskSignal": "",
                          "DriverMessage": "",
                          "GenericSource": ""
                        },
                        {
                          "Type": "Not Restarting",
                          "Time": 1523937716287977200,
                          "Message": "",
                          "DisplayMessage": "Policy allows no restarts",
                          "Details": {
                            "restart_reason": "Policy allows no restarts",
                            "fails_task": "true"
                          },
                          "FailsTask": true,
                          "RestartReason": "Policy allows no restarts",
                          "SetupError": "",
                          "DriverError": "",
                          "ExitCode": 0,
                          "Signal": 0,
                          "KillTimeout": 0,
                          "KillError": "",
                          "KillReason": "",
                          "StartDelay": 0,
                          "DownloadError": "",
                          "ValidationError": "",
                          "DiskLimit": 0,
                          "FailedSibling": "",
                          "VaultError": "",
                          "TaskSignalReason": "",
                          "TaskSignal": "",
                          "DriverMessage": "",
                          "GenericSource": ""
                        }
                      ]
                    }
                  },
                  "DeploymentStatus": null,
                  "CreateIndex": 1597,
                  "ModifyIndex": 1601,
                  "CreateTime": 1523937715194514700,
                  "ModifyTime": 1523937716451996200
                }|]
                e1 = ATerminated mempty 127
                e2 = AOther mempty
                expected = AllocationInfo (wrap $ fromList [("0da2d1ce-b058-4898-b1c6-a5372aa9b267", [e1, e2])])
            eitherDecode' json `shouldBe` Right expected
    describe "toEclStage" .
        it "recognises failure" $
            toEclStage (JobSummary "example" Dead (wrap $ fromList [("cache", 1)])) `shouldBe` Left AllocationDataRequired
    describe "determineFailure" $ do
        it "recognises non-zero exits" $
            determineFailure (exEvents [AOther mempty, ATerminated mempty 127, AOther mempty]) `shouldBe` Job.Failed (Job.NonZeroExitCode 127)
        it "falls back to OtherReason" $ do
            let def = Job.RunError Job.OtherFailure
            determineFailure (exEvents []) `shouldBe` def
            determineFailure (exEvents [ATerminated mempty 0]) `shouldBe` def
  where
    n = "uuid"
    exampleJobName = "example"
    nomad = wrap $ NomadJob n mempty [TaskGroup n [Task n mempty [mkArtifact (containerDownloadURI exampleBase echoContainer)] (Docker mempty "echo:virgil" ["hello world"]) (Env exampleJobName "http://example.com" "" "") (Resources 100 128) (Logs 1 10)] failRestartPolicy (EphemeralDisk 40) 1] ["dc1"]
    exEvents es = [AllocationInfo (wrap $ fromList [("ex", es)])]
