import jester, json, logging, options, sequtils, strutils
from re import re, find

import ./models, ./notion_client, ./service, ./version

const JSON = "application/json"

## Response Utilities
template halt(code: HttpCode,
              headers: RawHeaders,
              content: string) =
  ## Immediately replies with the specified request. This means any further
  ## code will not be executed after calling this template in the current
  ## route.
  bind TCActionSend, newHttpHeaders
  result[0] = CallbackAction.TCActionSend
  result[1] = code
  result[2] = if isSome(result[2]): some(result[2].get() & headers)
              else: some(headers)
  result[3] = content
  result.matched = true
  break allRoutes

template jsonResp(code: HttpCode,
                  body: string = "",
                  headersToSend: RawHeaders = @{:} ) =
  ## Immediately send a JSON response and stop processing the request.
  let reqOrigin =
    if request.headers.hasKey("Origin"): $(request.headers["Origin"])
    else: ""

  let corsHeaders =
    if cfg.knownOrigins.contains(reqOrigin):
      @{
        "Access-Control-Allow-Origin": reqOrigin,
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Methods": $(request.reqMethod),
        "Access-Control-Allow-Headers": "Authorization,X-CSRF-TOKEN"
      }
    else: @{:}

  halt(
    code,
    headersToSend & corsHeaders & @{
      "Content-Type": JSON,
      "Cache-Control": "no-cache"
    },
    body
  )

template dataResp(code: HttpCode,
                  data: JsonNode,
                  headersToSend: RawHeaders = @{:} ) =
  jsonResp(
    code,
    $(%* {
      "details": "",
      "data": data
    }),
    headersToSend)

template dataResp(data: JsonNode) = dataResp(Http200, data)

template statusResp(code: HttpCode,
                    details: string = "",
                    headersToSend: RawHeaders = @{:} ) =
  ## Helper to send a JSON response based on a given HTTP code.
  jsonResp(
    code,
    $(%* {
      "details": details
    }),
    headersToSend)

template errorResp(err: ref ApiError): void =
  debug err.respMsg & ( if err.msg.len > 0: ": " & err.msg else: "")
  if not err.parent.isNil: debug "    original exception: " & err.parent.msg
  statusResp(err.respCode, err.respMsg)

## CORS support
template optionsResp(allowedMethods: seq[HttpMethod]) =

  let reqOrigin =
    if request.headers.hasKey("Origin"): $(request.headers["Origin"])
    else: ""

  let corsHeaders =
    if cfg.knownOrigins.contains(reqOrigin):
      @{
        "Access-Control-Allow-Origin": reqOrigin,
        "Access-Control-Allow-Credentials": "true",
        "Access-Control-Allow-Methods": allowedMethods.mapIt($it).join(", "),
        "Access-Control-Allow-Headers": "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization,X-CSRF-TOKEN"
      }
    else: @{:}

  halt(
    Http200,
    corsHeaders,
    ""
  )

template withApiErrors(actions: untyped) =
  try: actions
  except JsonParsingError, ValueError:
    echo getCurrentException().getStackTrace()
    statusResp(Http400, getCurrentExceptionMsg())
  except ApiError: errorResp(cast[ref ApiError](getCurrentException()))
  except:
    let ex = getCurrentException()
    debug ex.getStackTrace
    errorResp(newApiError(ex, Http500, "Internal server error", ex.msg))

proc start*(cfg: HffEntryFormsApiConfig): void =

  if cfg.debug: setLogFilter(lvlDebug)

  settings:
    port = Port(cfg.port)
    appName = "/v1"

  routes:

    options "/version": optionsResp(@[HttpGet])

    get "/version":
      jsonResp(Http200, $(%("hff_entry_forms_api v" & HFF_ENTRY_FORMS_API_VERSION)))

    options "/add-page": optionsResp(@[HttpPost])

    post "/propose-event":
      withApiErrors:
        let ep = parseEventProposal(parseJson(request.body))
        if createProposedEvent(cfg, ep): statusResp(Http200)
        else: statusResp(Http500)

    options re".*": statusResp(Http404, "Not Found")
    get     re".*": statusResp(Http404, "Not Found")
    post    re".*": statusResp(Http404, "Not Found")
    put     re".*": statusResp(Http404, "Not Found")
    delete  re".*": statusResp(Http404, "Not Found")