{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}

{-|
Module      : GitLab.Types
Description : Haskell records corresponding to JSON data from GitLab API calls
Copyright   : (c) Rob Stewart, Heriot-Watt University, 2019
License     : BSD3
Maintainer  : robstewart57@gmail.com
Stability   : stable
-}
module GitLab.Types
  (
    GitLab
  , GitLabState(..)
  , GitLabServerConfig(..)
  , defaultGitLabServer
  , Member(..)
  , Namespace(..)
  , Links(..)
  , Owner(..)
  , Permissions(..)
  , Project(..)
  , ProjectStats(..)
  , User(..)
  , Milestone(..)
  , TimeStats(..)
  , Issue(..)
  , Pipeline(..)
  , Commit(..)
  , CommitStats(..)
  , Diff(..)
  , Repository(..)
  , Job(..)
  , Artifact(..)
  , Group(..)
  , GroupShare(..)
  , Branch(..)
  , RepositoryFile(..)
  , MergeRequest(..)
  ) where

import Data.Aeson
import Data.Text (Text)
import GHC.Generics
import Network.HTTP.Conduit
import Control.Monad.Trans.Reader

-- | type synonym for all GitLab actions.
type GitLab m a = ReaderT GitLabState m a

-- | state used by GitLab actions, used internally. 
data GitLabState =
  GitLabState
  { serverCfg :: GitLabServerConfig
  , httpManager :: Manager
  }

-- | configuration data specific to a GitLab server.
data GitLabServerConfig =
  GitLabServerConfig
  { url :: Text
  , token :: Text -- ^ personal access token, see <https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html>
  , timeout :: Int -- ^ milliseconds
  , retries :: Int -- ^ how many times to retry a HTTP request before giving up and returning an error.
  }

-- | default settings, the 'url' and 'token' values will need to be overwritten.
defaultGitLabServer :: GitLabServerConfig
defaultGitLabServer =
  GitLabServerConfig
  { url = "https://gitlab.com"
  , token = ""
  , timeout = 15000000 -- 15 seconds
  , retries = 5
  }

-- | member of a project.
data Member =
  Member
  { member_id :: Int
  , member_name :: Text
  , member_username :: Text
  , member_state :: Text
  , member_avatar_uri :: Maybe Text
  , member_web_url :: Maybe Text
  , access_level :: Int
  , expires_at :: Maybe Text
  } deriving (Generic, Show)

-- | namespaces.
data Namespace =
  Namespace
  { namespace_id :: Int
  , namespace_name :: Text
  , namespace_path :: Text
  , kind :: Text
  , full_path :: Text
  , parent_id :: Maybe Text
  } deriving (Generic, Show, Eq)

-- | links.
data Links =
  Links
  { self :: Text
  , issues :: Text
  , merge_requests :: Text
  , repo_branches :: Text
  , link_labels :: Text
  , link_events :: Text
  , members :: Text
  } deriving (Generic, Show, Eq)

-- | owners.
data Owner =
  Ownwer
  { owner_id :: Int
  , owner_name :: Text
  , owner_username :: Text
  , state :: Text
  , owner_avatar_url :: Maybe Text
  , owner_web_url :: Text
  } deriving (Generic, Show, Eq)

-- | permissions.
data Permissions =
  Permissions
  { project_access :: Maybe Object
  , group_access :: Maybe Object
  } deriving (Generic, Show, Eq)

-- | projects.
data Project =
  Project
  { project_id :: Int
  , description :: Maybe Text
  , project_name :: Text
  , name_with_namespace :: Text
  , project_path :: Text
  , path_with_namespace :: Text
  , project_created_at :: Text
  , default_branch :: Maybe Text
  , tag_list:: [Text] -- check
  , ssh_url_to_repo:: Text
  , http_url_to_repo:: Text
  , project_web_url:: Text
  , readme_url :: Maybe Text -- check
  , project_avatar_url:: Maybe Text -- check
  , star_count:: Int
  , forks_count:: Int
  , last_activity_at:: Text
  , namespace :: Namespace
  , _links :: Maybe Links
  , archived :: Maybe Bool
  , visibility :: Maybe Text
  , owner :: Maybe Owner
  , resolve_outdated_diff_discussions :: Maybe Bool
  , container_registry_enabled :: Maybe Bool
  , issues_enabled :: Maybe Bool
  , merge_requests_enabled:: Maybe Bool
  , wiki_enabled :: Maybe Bool
  , jobs_enabled :: Maybe Bool
  , snippets_enabled :: Maybe Bool
  , shared_runners_enabled :: Maybe Bool
  , lfs_enabled :: Maybe Bool
  , creator_id :: Maybe Int
  , forked_from_project :: Maybe Project
  , import_status :: Maybe String
  , open_issues_count :: Maybe Int
  , public_jobs :: Maybe Bool
  , ci_config_path :: Maybe Text -- check null
  , shared_with_groups :: Maybe [Object]
  , only_allow_merge_if_pipeline_succeeds :: Maybe Bool
  , request_access_enabled :: Maybe Bool
  , only_allow_merge_if_all_discussions_are_resolved :: Maybe Bool
  , printing_merge_request_link_enabled :: Maybe Bool
  , merge_method:: Maybe Text
  , permissions :: Maybe Permissions
  , project_stats :: Maybe ProjectStats
  } deriving (Generic, Show, Eq)

data ProjectStats =
  ProjectStats
  { commit_count :: Int
  , storage_size :: Int
  , repository_size :: Int
  , wiki_size :: Maybe Int
  , lfs_objects_size :: Maybe Int
  , job_artifacts_size :: Maybe Int
  , packages_size :: Maybe Int
  } deriving (Generic, Show, Eq)

-- | registered users.
data User =
  User
  { user_id :: Int
  , user_username :: Text
  , user_name :: Text
  , user_state :: Text
  , user_avatar_uri :: Maybe Text
  , user_web_url :: Maybe Text
  } deriving (Generic, Show, Eq)

-- | project milestones.
data Milestone =
  Milestone
  { milestone_project_id :: Int
  , milestone_description :: Text
  , milestone_state :: Text
  , due_date :: Maybe Text
  , milestone_iid :: Int
  , milestone_created_at :: Maybe Text
  , milestone_title :: Text
  , milestone_id :: Int
  , milestone_updated_at :: Text
  } deriving (Generic, Show, Eq)

-- | time stats.
data TimeStats =
  TimeStats
  { time_estimate :: Int
  , total_time_spent :: Int
  , humane_time_estimate :: Maybe Int
  , human_total_time_spent :: Maybe Int
  } deriving (Generic, Show, Eq)

-- | project issues.
data Issue =
  Issue
  { issue_state :: Text
  , issue_description :: Text
  , issue_author :: User
  , milestone :: Maybe Milestone
  , issue_project_id :: Int
  , assignees :: Maybe [User]
  , assignee :: Maybe User
  , updated_at :: Text
  , closed_at :: Maybe Text
  , closed_by :: Maybe User
  , issue_id :: Int
  , issue_title :: Text
  , issue_created_at :: Text
  , iid :: Int
  , issue_labels :: [Text]
  , upvotes :: Int
  , downvotes :: Int
  , user_notes_count :: Int
  , issue_due_date :: Maybe Text
  , issue_web_url :: Text
  , confidential :: Bool
  , weight :: Maybe Text -- Int?
  , discussion_locked :: Maybe Bool
  , time_stats :: TimeStats
  } deriving (Generic, Show, Eq)

-- | project pipelines
data Pipeline =
  Pipeline
  { pipeline_id :: Int
  , sha :: Text
  , pipeline_ref :: Text
  , pipeline_status :: Text
  , pipeline_web_url :: Maybe Text
  } deriving (Generic, Show)

-- | code commits.
data Commit =
  Commit
  { commit_id :: Text
  , short_id :: Text
  , title :: Text
  , author_name :: Text
  , author_email :: Text
  , authored_date :: Text
  , committer_name :: Text
  , committer_email :: Text
  , committed_date :: Text
  , commit_created_at :: Text
  , message :: Text
  , parent_ids :: Maybe [Text]
  , last_pipeline :: Maybe Pipeline
  , commit_stats :: Maybe CommitStats
  , commit_status :: Maybe Text
  } deriving (Generic, Show)

-- | commit stats.
data CommitStats =
  Stats
  { additions :: Int
  , deletions :: Int
  , total :: Int
  } deriving (Generic, Show)

data Diff =
  Diff
  { diff :: Text
  , new_path :: Text
  , old_path :: Text
  , a_mode :: Maybe Text
  , b_mode :: Maybe Text
  , new_file :: Bool
  , renamed_file :: Bool
  , deleted_file :: Bool
  } deriving (Generic, Show)

-- | repositories.
data Repository =
  Repository
  { repository_id :: Text
  , repository_name :: Text
  , repository_type :: Text
  , repository_path :: Text
  , mode :: Text
  } deriving (Generic, Show)

-- | jobs.
data Job =
  Job
  {
    commit :: Commit
  , coverage :: Maybe Text -- ?
  , created_at :: Text
  , started_at :: Text
  , finished_at :: Text
  , duration :: Double
  , artifacts_expire_at :: Maybe Text
  , id :: Int
  , name :: Text
  , pipeline :: Pipeline
  , job_ref :: Text
  , artifacts :: [Artifact]
  -- , runner :: Maybe Text
  , stage :: Text
  , status :: Text
  , tag :: Bool
  , job_web_url :: Text
  , user :: User
  } deriving (Generic, Show)

-- | artifacts.
data Artifact =
  Artifact
  { file_type :: Text
  , size :: Int
  , filename :: Text
  , file_format :: Maybe Text
  } deriving (Generic, Show)

-- | groups.
data Group =
  Group
  {
    group_id :: Int
  , group_name :: Text
  , group_path :: Text
  , group_description :: Text
  , group_visibility :: Text
  , group_lfs_enabled :: Bool
  , group_avatar_url :: Maybe Text
  , group_web_url :: Text
  , group_request_access_enabled :: Bool
  , group_full_name :: Text
  , group_full_path :: Text
  , group_file_template_project_id :: Maybe Int
  , group_parent_id :: Maybe Int
  } deriving (Generic, Show)


-- | response to sharing a project with a group.
data GroupShare =
  GroupShare
  { share_id :: Int
  , share_project_id :: Int
  , share_group_id :: Int
  , share_group_access :: Int
  , share_expires_at :: Maybe Text
  } deriving (Generic, Show)

-- | code branches.
data Branch =
  Branch
  { branch_name :: Text
  , merged :: Bool
  , protected :: Bool
  , branch_default :: Bool
  , developers_can_push :: Bool
  , developers_can_merge :: Bool
  , can_push :: Bool
  , branch_commit :: Commit
  } deriving (Generic, Show)

-- | files in a repository.
data RepositoryFile =
  RepositoryFile
  { repository_file_file_name :: Text
  , repository_file_file_path :: Text
  , repository_file_size :: Int
  , encoding :: Text
  , content :: Text
  , content_sha256 :: Text
  , ref :: Text
  , blob_id :: Text
  , repository_file_commit_id :: Text
  , last_commit_id :: Text
  } deriving (Generic, Show)

-- | project merge requests.
data MergeRequest =
  MergeRequest
  { merge_request_id :: Int
  , merge_request_iid :: Int
  , merge_request_project_id :: Int
  , merge_request_title :: Text
  , merge_request_description :: Text
  , merge_request_state :: Text
  , merge_request_merged_by :: Maybe User
  , merge_request_merged_at :: Maybe Text
  , merge_request_closed_by :: Maybe User
  , merge_request_closed_at :: Maybe Text
  , merge_request_created_at :: Text
  , merge_request_updated_at :: Text
  , merge_request_target_branch :: Text
  , merge_request_source_branch :: Text
  , merge_request_upvotes :: Int
  , merge_request_downvotes :: Int
  , merge_request_author :: User
  , merge_request_assignee :: Maybe User
  , merge_request_source_project_id :: Int
  , merge_request_target_project_id :: Int
  , merge_request_labels :: [Text]
  , merge_request_work_in_progress :: Bool
  , merge_request_milestone :: Maybe Milestone
  , merge_request_merge_when_pipeline_succeeds :: Bool
  , merge_request_merge_status :: Text
  , merge_request_sha :: Text
  , merge_request_merge_commit_sha :: Maybe Text
  , merge_request_user_notes_count ::Int
  , merge_request_discussion_locked :: Maybe Bool
  , merge_request_should_remove_source_branch :: Maybe Bool
  , merge_request_force_remove_source_branch :: Maybe Bool
  -- , merge_request_allow_collaboration :: Bool
  -- , merge_request_allow_maintainer_to_push :: Bool
  , merge_request_web_url :: Text
  , merge_request_time_stats :: TimeStats
  , merge_request_squash :: Bool
  , merge_request_approvals_before_merge :: Maybe Bool -- ?
  } deriving (Generic, Show)

-----------------------------
-- JSON GitLab parsers below
-----------------------------

bodyNoPrefix :: String -> String
bodyNoPrefix "project_id" = "id"
bodyNoPrefix "namespace_id" = "id"
bodyNoPrefix "owner_id" = "id"
bodyNoPrefix "user_id" = "id"
bodyNoPrefix "repository_id" = "id"
bodyNoPrefix "project_name" = "name"
bodyNoPrefix "repository_name" = "name"
bodyNoPrefix "project_path" = "path"
bodyNoPrefix "namespace_name" = "name"
bodyNoPrefix "user_name" = "name"
bodyNoPrefix "namespace_path" = "path"
bodyNoPrefix "owner_name" = "name"
bodyNoPrefix "owner_avatar_url" = "avatar_url"
bodyNoPrefix "user_avatar_url" = "avatar_url"
bodyNoPrefix "owner_web_url" = "web_url"
bodyNoPrefix "job_web_url" = "web_url"
bodyNoPrefix "project_avatar_url" = "avatar_url"
bodyNoPrefix "project_web_url" = "web_url"
bodyNoPrefix "member_id" = "id"
bodyNoPrefix "member_name" = "name"
bodyNoPrefix "member_avatar_url" = "avatar_url"
bodyNoPrefix "member_web_url" = "we_url"
bodyNoPrefix "user_web_url" = "we_url"
bodyNoPrefix "member_username" = "username"
bodyNoPrefix "owner_username" = "username"
bodyNoPrefix "user_username" = "username"
bodyNoPrefix "pipeline_id" = "id"
bodyNoPrefix "pipeline_web_url" = "web_url"
bodyNoPrefix "commit_id" = "id"
bodyNoPrefix "member_state" = "state"
bodyNoPrefix "user_state" = "state"
bodyNoPrefix "issue_state" = "state"
bodyNoPrefix "issue_description" = "description"
bodyNoPrefix "issue_author" = "author"
bodyNoPrefix "issue_project_id" = "project_id"
bodyNoPrefix "issue_id" = "id"
bodyNoPrefix "issue_title" = "title"
bodyNoPrefix "issue_due_date" = "due_date"
bodyNoPrefix "issue_web_url" = "web_url"
bodyNoPrefix "commit_status" = "status"
bodyNoPrefix "pipeline_status" = "status"
bodyNoPrefix "issue_labels" = "labels"
bodyNoPrefix "link_labels" = "labels"
bodyNoPrefix "issue_created_at" = "created_at"
bodyNoPrefix "commit_created_at" = "created_at"
bodyNoPrefix "project_created_at" = "created_at"
bodyNoPrefix "milestone_created_at" = "created_at"
bodyNoPrefix "link_events" = "events"
bodyNoPrefix "repository_type" = "type"
bodyNoPrefix "repository_path" = "path"

bodyNoPrefix "event_title" = "title"
bodyNoPrefix "event_project_id" = "project_id"

bodyNoPrefix "job_ref" = "ref"
bodyNoPrefix "pipeline_ref" = "ref"

bodyNoPrefix "branch_name" = "name"
bodyNoPrefix "branch_default" = "default"
bodyNoPrefix "branch_commit" = "commit"

bodyNoPrefix "repository_file_file_name" = "file_name"
bodyNoPrefix "repository_file_file_path" = "file_path"
bodyNoPrefix "repository_file_size" = "size"
bodyNoPrefix "repository_file_commit_id" = "commit_id"

bodyNoPrefix "merge_request_id" = "id"
bodyNoPrefix "merge_request_iid" = "iid"
bodyNoPrefix "merge_request_project_id" = "project_id"
bodyNoPrefix "merge_request_title" = "title"
bodyNoPrefix "merge_request_description" = "description"
bodyNoPrefix "merge_request_state" = "state"
bodyNoPrefix "merge_request_merged_by" = "merged_by"
bodyNoPrefix "merge_request_merged_at" = "merged_at"
bodyNoPrefix "merge_request_closed_by" = "closed_by"
bodyNoPrefix "merge_request_closed_at" = "closed_at"
bodyNoPrefix "merge_request_created_at" = "created_at"
bodyNoPrefix "merge_request_updated_at" = "updated_at"
bodyNoPrefix "merge_request_target_branch" = "target_branch"
bodyNoPrefix "merge_request_source_branch" = "source_branch"
bodyNoPrefix "merge_request_upvotes" = "upvotes"
bodyNoPrefix "merge_request_downvotes" = "downvotes"
bodyNoPrefix "merge_request_author" = "author"
bodyNoPrefix "merge_request_assignee" = "assignee"
bodyNoPrefix "merge_request_source_project_id" = "source_project_id"
bodyNoPrefix "merge_request_target_project_id" = "target_project_id"
bodyNoPrefix "merge_request_labels" = "labels"
bodyNoPrefix "merge_request_work_in_progress" = "work_in_progress"
bodyNoPrefix "merge_request_milestone" = "milestone"
bodyNoPrefix "merge_request_merge_when_pipeline_succeeds" = "merge_when_pipeline_succeeds"
bodyNoPrefix "merge_request_merge_status" = "merge_status"
bodyNoPrefix "merge_request_sha" = "sha"
bodyNoPrefix "merge_request_merge_commit_sha" = "merge_commit_sha"
bodyNoPrefix "merge_request_user_notes_count" = "user_notes_count"
bodyNoPrefix "merge_request_discussion_locked" = "discussion_locked"
bodyNoPrefix "merge_request_should_remove_source_branch" = "should_remove_source_branch"
bodyNoPrefix "merge_request_force_remove_source_branch" = "force_remove_source_branch"
bodyNoPrefix "merge_request_allow_collaboration" = "allow_collaboration"
bodyNoPrefix "merge_request_allow_maintainer_to_push" = "allow_maintainer_to_push"
bodyNoPrefix "merge_request_web_url" = "web_url"
bodyNoPrefix "merge_request_time_stats" = "time_stats"
bodyNoPrefix "merge_request_squash" = "squash"
bodyNoPrefix "merge_request_approvals_before_merge" = "approvals_before_merge"
bodyNoPrefix "project_stats" = "statistics"
bodyNoPrefix "commit_stats" = "stats"

bodyNoPrefix "share_id" = "id"
bodyNoPrefix "share_project_id" = "project_id"
bodyNoPrefix "share_group_id" = "group_id"
bodyNoPrefix "share_group_access" = "group_access"
bodyNoPrefix "share_expires_at" = "expires_at"

bodyNoPrefix "group_id" = "id"
bodyNoPrefix "group_name" = "name"
bodyNoPrefix "group_path" = "path"
bodyNoPrefix "group_description" = "description"
bodyNoPrefix "group_visibility" = "visibility"
bodyNoPrefix "group_lfs_enabled" = "lfs_enabled"
bodyNoPrefix "group_avatar_url" = "avatar_url"
bodyNoPrefix "group_web_url" = "web_url"
bodyNoPrefix "group_request_access_enabled" = "request_access_enabled"
bodyNoPrefix "group_full_name" = "full_name"
bodyNoPrefix "group_full_path" = "full_path"
bodyNoPrefix "group_file_template_project_id" = "file_template_project_id"
bodyNoPrefix "group_parent_id" = "parent_id"


-- TODO field names for Issues data type
bodyNoPrefix s = s

instance FromJSON TimeStats where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Milestone where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Issue where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON User where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Commit where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON CommitStats where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Pipeline where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Member where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Permissions where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Owner where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Links where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Namespace where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Project where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON ProjectStats where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Repository where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Job where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Artifact where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Group where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON GroupShare where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Branch where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON RepositoryFile where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON MergeRequest where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })

instance FromJSON Diff where
  parseJSON = genericParseJSON
              (defaultOptions
               { fieldLabelModifier = bodyNoPrefix })