{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE NoImplicitPrelude     #-}
{-# LANGUAGE OverloadedStrings     #-}
{-# LANGUAGE RecordWildCards       #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE StrictData            #-}
{-# LANGUAGE TemplateHaskell       #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeFamilies          #-}
module Headroom.Command.RunSpec
  ( spec
  )
where

import           Headroom.Command.Run
import           Headroom.Configuration.Types   ( CtHeaderFnConfigs
                                                , HeaderFnConfig(..)
                                                , HeaderFnConfigs(..)
                                                , HeaderSyntax(..)
                                                , LicenseType(..)
                                                , UpdateCopyrightConfig(..)
                                                )
import           Headroom.Data.EnumExtra        ( EnumExtra(..) )
import           Headroom.Data.Has              ( Has(..) )
import           Headroom.Data.Lens             ( suffixLenses
                                                , suffixLensesFor
                                                )
import           Headroom.Data.TextExtra        ( fromLines )
import           Headroom.FileSystem            ( FileSystem(..) )
import           Headroom.FileType.Types        ( FileType(..) )
import           Headroom.Meta                  ( TemplateType )
import           Headroom.Template              ( Template(..) )
import           Headroom.Types                 ( CurrentYear(..) )
import           Headroom.Variables             ( mkVariables )
import           RIO                     hiding ( assert )
import qualified RIO.Map                       as M
import qualified RIO.NonEmpty                  as NE
import qualified RIO.Text                      as T
import           Test.Hspec
import           Test.Hspec.QuickCheck          ( prop )
import           Test.QuickCheck         hiding ( sample )
import           Test.QuickCheck.Monadic


data TestEnv = TestEnv
  { envLogFunc         :: LogFunc
  , envCurrentYear     :: CurrentYear
  , envFileSystem      :: FileSystem (RIO TestEnv)
  , envHeaderFnConfigs :: CtHeaderFnConfigs
  }

suffixLenses ''TestEnv
suffixLensesFor ["fsFindFilesByExts", "fsLoadFile"] ''FileSystem

instance HasLogFunc TestEnv where
  logFuncL = envLogFuncL

instance Has CtHeaderFnConfigs TestEnv where
  hasLens = envHeaderFnConfigsL

instance Has CurrentYear TestEnv where
  hasLens = envCurrentYearL

instance Has (FileSystem (RIO TestEnv)) TestEnv where
  hasLens = envFileSystemL


spec :: Spec
spec = do
  describe "loadBuiltInTemplates" $ do
    it "should load correct number of built-in templates" $ do
      templates <- runRIO env $ loadBuiltInTemplates BSD3
      M.size templates `shouldBe` 11


  describe "loadTemplateFiles" $ do
    it "should load templates from given paths" $ do
      let env' =
            env
              & (envFileSystemL . fsFindFilesByExtsL .~ fsFindFilesByExts')
              & (envFileSystemL . fsLoadFileL .~ fsLoadFile')
          fsFindFilesByExts' "test-dir" _ = pure ["haskell.mustache"]
          fsFindFilesByExts' _          _ = throwString "INVALID CONDITION"
          fsLoadFile' "haskell.mustache" = pure "template content"
          fsLoadFile' _                  = throwString "INVALID CONDITION"
      templates <- runRIO env' $ loadTemplateFiles ["test-dir"]
      M.size templates `shouldBe` 1
      M.member Haskell templates `shouldBe` True


  describe "typeOfTemplate" $ do
    let fileTypes = fmap (T.toLower . enumToText) (allValues @FileType)
        templateExt         = NE.head $ templateExtensions @TemplateType
        pathGen             = elements $ fmap (<> "." <> templateExt) fileTypes
        prop_typeOfTemplate = monadicIO $ do
          path   <- T.unpack <$> pick pathGen
          result <- run (runRIO env $ typeOfTemplate path)
          assert $ isJust result

    prop "should detect type of template from template path" prop_typeOfTemplate


  describe "postProcessHeader'" $ do
    it "should perform expected post-processing on license header" $ do
      let sample = fromLines
            [ "-- Copyright (c) 2018-2019 1st Author"
            , "Copyright (c) 2017 2nd Author"
            ]
          expected = fromLines
            [ "-- Copyright (c) 2018-2019 1st Author"
            , "-- Copyright (c) 2017-2020 2nd Author"
            ]
          vars   = mkVariables [("sndAuthor", "2nd Author")]
          syntax = LineComment "--"
      actual <- runRIO env $ postProcessHeader' syntax vars sample
      actual `shouldBe` expected

  describe "sanitizeHeader" $ do
    it "does nothing when block comment syntax used" $ do
      let sample = fromLines ["{-", "foo", "bar", "-}"]
          syntax = BlockComment "{-" "-}"
      sanitizeHeader syntax sample `shouldBe` sample

    it "adds missing single-line comment syntax" $ do
      let sample   = fromLines ["-- first", "second", "-- third"]
          syntax   = LineComment "--"
          expected = fromLines ["-- first", "-- second", "-- third"]
      sanitizeHeader syntax sample `shouldBe` expected


env :: TestEnv
env = TestEnv { .. }
 where
  envLogFunc     = mkLogFunc (\_ _ _ _ -> pure ())
  envCurrentYear = CurrentYear 2020
  envFileSystem  = FileSystem { fsCreateDirectory     = undefined
                              , fsDoesFileExist       = undefined
                              , fsFindFiles           = undefined
                              , fsFindFilesByExts     = undefined
                              , fsFindFilesByTypes    = undefined
                              , fsGetCurrentDirectory = undefined
                              , fsListFiles           = undefined
                              , fsLoadFile            = undefined
                              }
  envHeaderFnConfigs = HeaderFnConfigs
    { hfcsUpdateCopyright = HeaderFnConfig
                              { hfcEnabled = True
                              , hfcConfig  = UpdateCopyrightConfig
                                               { uccSelectedAuthors =
                                                 Just $ "{{ sndAuthor }}" :| []
                                               }
                              }
    }