namespace {{packageName}}

open Microsoft.AspNetCore.Authentication
open Microsoft.AspNetCore.Authentication.Cookies
open Microsoft.Extensions.DependencyInjection
open Microsoft.AspNetCore.Http
open Microsoft.AspNetCore.Authentication.OAuth
open System
open Giraffe
open FSharp.Control.Tasks.V2.ContextInsensitive
open Microsoft.Extensions.Configuration
open AspNet.Security.ApiKey.Providers.Extensions
open AspNet.Security.ApiKey.Providers.Events


module AuthSchemes =

  let accessDenied : HttpHandler = setStatusCode 401 >=> text "Access Denied"

  let buildGoogle (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) =
    builder.AddGoogle(fun googleOptions -> CustomHandlers.setOAuthOptions "Google" googleOptions scopes settings)

  let buildGitHub (builder:AuthenticationBuilder) name authorizationUrl scopes (settings:IConfiguration) =
    builder.AddGitHub(fun githubOptions -> CustomHandlers.setOAuthOptions "GitHub" githubOptions scopes settings)

  let buildOAuth (builder:AuthenticationBuilder) (name:string) authorizationUrl scopes (settings:IConfiguration) =
    builder.AddOAuth(name, (fun (options:OAuthOptions) ->
      options.AuthorizationEndpoint <- authorizationUrl
      options.TokenEndpoint <- settings.[name + "TokenUrl"]
      options.CallbackPath <- PathString(settings.[name + "CallbackPath"])
      CustomHandlers.setOAuthOptions "{{name}}" options scopes settings
  ))

  let OAuthBuilders = Map.empty.Add("Google", buildGoogle).Add("GitHub", buildGitHub)

  let checkEnvironment (settings:IConfiguration) name =
    if (isNull settings.[name + "ClientId"]) then
      raise (Exception(name + "ClientId is not set."))
    else if (isNull settings.[name + "ClientSecret"]) then
      raise (Exception((name + "ClientSecret is not set.")))

  let getOAuthBuilder settings name =
    // check that "xxxClientId" and "xxxClientSecret" configuration variables have been set for all OAuth providers
    checkEnvironment settings name
    if OAuthBuilders.ContainsKey(name) then
      OAuthBuilders.[name]
    else
      buildOAuth

  let configureOAuth (settings:IConfiguration) services =
    {{#authMethods}}
    {{#isOAuth}}
    (getOAuthBuilder settings "{{name}}") services "{{name}}" "{{authorizationUrl}}" [{{#scopes}}"{{scope}}";{{/scopes}}] settings
    {{/isOAuth}}
    {{/authMethods}}

  let buildApiKeyAuth name (services:AuthenticationBuilder) =
    services.AddApiKey(fun options ->
      options.Header <- name
      options.HeaderKey <- String.Empty
      let events = ApiKeyEvents()
      options.Events <- CustomHandlers.setApiKeyEvents name events
    )

  let configureApiKeyAuth (settings:IConfiguration) services =
    {{#authMethods}}
    {{#isApiKey}}
    {{#isKeyInHeader}}
    buildApiKeyAuth "{{name}}" services
    {{/isKeyInHeader}}
    {{^isKeyInHeader}}
    raise (NotImplementedException("API key security scheme outside of header has not yet been implemented"))
    {{/isKeyInHeader}}
    {{/isApiKey}}
    {{/authMethods}}


  let configureCookie (builder:AuthenticationBuilder) =
      builder.AddCookie(CustomHandlers.cookieAuth)

  let configureServices (services:IServiceCollection) =
    let serviceProvider = services.BuildServiceProvider()
    let settings = serviceProvider.GetService<IConfiguration>()
    services.AddAuthentication(fun o -> o.DefaultScheme <- CookieAuthenticationDefaults.AuthenticationScheme)
    |> configureOAuth settings
    |> configureApiKeyAuth settings
    |> configureCookie

  let (|||) v1 v2 =
      match v1 with
      | Some v -> v1
      | None -> v2

  // this can be replaced with ctx.GetCookieValue in Giraffe >= 3.6
  let getCookieValue (ctx:HttpContext) (key : string)  =
        match ctx.Request.Cookies.TryGetValue key with
        | true , cookie -> Some cookie
        | false, _ -> None
