/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2023 Martin Preuss, all rights reserved.
 *
 * The license for this file can be found in the file COPYING which you
 * should have received along with this file.
 ****************************************************************************/

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

//#define DISABLE_DEBUGLOG


#include "aqhome/http/httpservice_http.h"
#include "aqhome/http/httpservice_conf.h"
#include "aqhome/http/httpservice_p.h"
#include "aqhome/http/urlhandler.h"

#include <gwenhywfar/db.h>
#include <gwenhywfar/buffer.h>
#include <gwenhywfar/text.h>
#include <gwenhywfar/debug.h>



#ifdef GWEN_INHERIT_REF
  GWEN_INHERIT_REF(AQH_SERVICE, AQH_HTTP_SERVICE)
#else
  extern uint32_t AQH_HTTP_SERVICE_ID;
#endif



/* ------------------------------------------------------------------------------------------------
 * forward declarations
 * ------------------------------------------------------------------------------------------------
 */

static void _writeDefaultResponseHeaderToBuffer(AQH_SERVICE *sv, int contentLength, GWEN_BUFFER *buf);
static GWEN_BUFFER *_getPageFilePath(const AQH_SERVICE *sv, const char *langFolder, const char *fileName);
static AQH_HTTP_URLHANDLER *_findMatchingUrlHandler(AQH_SERVICE *sv, const char *path);



/* ------------------------------------------------------------------------------------------------
 * implementations
 * ------------------------------------------------------------------------------------------------
 */


GWEN_MSG *AQH_HttpService_HandleHttpRequest(AQH_SERVICE *sv, GWEN_MSG_ENDPOINT *endpoint, const GWEN_MSG *msgReceived)
{
  AQH_HTTP_SERVICE *xsv;
  AQH_HTTP_REQUEST *rq;
  const char *s;
  const char *cmd;
  const char *protocol;
  AQH_SESSION *session=NULL;
  GWEN_MSG *msgResponse;
  int rv;
  AQH_HTTP_URLHANDLER *uh;

  xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv);
  if (xsv==NULL) {
    DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object");
    return NULL;
  }

  rq=AQH_HttpRequest_new(endpoint, msgReceived);
  if (rq==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "Could not create valie request from incoming message");
    return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", "HTTP/1.1", NULL);
  }

  cmd=AQH_HttpRequest_GetCommand(rq);
  protocol=AQH_HttpRequest_GetProtocol(rq);

#if 0
  s=AQH_HttpRequest_GetModuleName(rq);
  if (s && *s) {
    m=AQH_HttpService_GetModule(sv, s);
    if (m==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "Module \"%s\" not found", s);
    }
    else
      AQH_HttpRequest_SetModule(rq, m);
  }
#endif

  s=AQH_HttpRequest_GetSessionId(rq);
  if (s && *s) {
    session=AQH_HttpService_GetSession(sv, s);
    if (session==NULL) {
      DBG_INFO(AQH_LOGDOMAIN, "Session \"%s\" not found", s);
    }
    else {
      GWEN_TIMESTAMP *ts;

      DBG_INFO(AQH_LOGDOMAIN, "Found session \"%s\"", s);
      ts=GWEN_Timestamp_NowInLocalTime();
      AQH_Session_SetTimestampLastAccess(session, ts);
      GWEN_Timestamp_free(ts);
      AQH_Session_AddRuntimeFlags(session, AQH_SESSION_RTFLAGS_MODIFIED);
      AQH_HttpRequest_SetSession(rq, session);
    }
  }

  s=AQH_HttpRequest_GetUrlPath(rq);
  DBG_INFO(AQH_LOGDOMAIN, "Received \"%s\" request for url \"%s\"", cmd?cmd:"<no cmd>", s);
  uh=_findMatchingUrlHandler(sv, s);
  if (uh==NULL) {
    DBG_INFO(AQH_LOGDOMAIN, "No matching handler found for url \"%s\"", s);
    AQH_HttpRequest_free(rq);
    return AQH_HttpService_CreateResponseMsg(sv, 404, "Not found", protocol?protocol:"http/1.1", NULL);
  }

  rv=AQH_HttpUrlHandler_HandleMessage(uh, rq);
  if (rv<0) {
    DBG_INFO(AQH_LOGDOMAIN, "Error handling request");
    AQH_HttpRequest_free(rq);
    return AQH_HttpService_CreateResponseMsg(sv, 500, "Internal server error", protocol?protocol:"http/1.1", NULL);
  }
  msgResponse=AQH_HttpRequest_TakeResponseMsg(rq);
  AQH_HttpRequest_free(rq);
  return msgResponse;
}



void AQH_HttpService_AddUrlHandler(AQH_SERVICE *sv, AQH_HTTP_URLHANDLER *urlHandler)
{
  AQH_HTTP_SERVICE *xsv;

  xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv);
  if (xsv==NULL) {
    DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object");
  }
  else {
    AQH_HttpUrlHandler_List_Add(urlHandler, xsv->urlHandlerList);
  }
}



void AQH_HttpService_SetRequestPerms(AQH_SERVICE *sv, AQH_HTTP_REQUEST *rq)
{
  AQH_MODULE *m;
  AQH_SESSION *session;

  AQH_HttpRequest_SetModulePerms(rq, 0);
  m=AQH_HttpRequest_GetModule(rq);
  session=AQH_HttpRequest_GetSession(rq);
  if (m) {
    DBG_INFO(AQH_LOGDOMAIN, "Presetting module's guest perms");
    AQH_HttpRequest_SetModulePerms(rq, AQH_Module_GetGuestPerms(m));
    if (session) {
      AQH_USER * user;

      user=AQH_Session_GetUser(session);
      if (user) {
        AQH_MODULE_PERMS_LIST *permsList;

        permsList=AQH_User_GetModulePermList(user);
        if (permsList) {
          AQH_MODULE_PERMS *userPerms;

          userPerms=AQH_ModulePerms_List_GetByModuleId(AQH_User_GetModulePermList(user), AQH_Module_GetId(m));
          if (userPerms) {
            DBG_INFO(AQH_LOGDOMAIN, "Using user perms for module");
            AQH_HttpRequest_SetModulePerms(rq, AQH_ModulePerms_GetPerms(userPerms));
          }
        }
        else {
          DBG_INFO(AQH_LOGDOMAIN, "No module perms list in user, using module's guest perms");
        }
      }
      else {
        DBG_INFO(AQH_LOGDOMAIN, "Session but no user, SNH!");
      }
    }
    else {
      DBG_INFO(AQH_LOGDOMAIN, "No session, using module's guest perms");
    }
  }
  else {
    DBG_INFO(AQH_LOGDOMAIN, "No module, no perms");
  }
}



void AQH_HttpService_AddStatusLine(AQH_SERVICE *sv, int code, const char *msg, const char *proto, GWEN_BUFFER *buf)
{
  GWEN_Buffer_AppendArgs(buf, "%s %03d %s \r\n", proto?proto:"HTTP/1.1", code, msg?msg:"");
}



void AQH_HttpService_AddHeader(AQH_SERVICE *sv, GWEN_DB_NODE *dbHeader, GWEN_BUFFER *buf)
{
  GWEN_DB_NODE *dbVar;

  dbVar=GWEN_DB_GetFirstVar(dbHeader);
  while (dbVar) {
    GWEN_DB_NODE *dbVal;

    /* only handle first value */
    dbVal=GWEN_DB_GetFirstValue(dbVar);
    if (dbVal) {
      const char *sVar;

      sVar=GWEN_DB_VariableName(dbVar);
      if (sVar && *sVar) {
        GWEN_DB_NODE_TYPE vtype;

        vtype=GWEN_DB_GetValueType(dbVal);
        if (vtype==GWEN_DB_NodeType_ValueChar) {
          const char *sValue;

          sValue=GWEN_DB_GetCharValueFromNode(dbVal);
          if (sValue)
            GWEN_Buffer_AppendArgs(buf, "%s: %s\r\n", sVar, sValue);
        } /* if char */
        else if (vtype==GWEN_DB_NodeType_ValueInt) {
          int i;

          i=GWEN_DB_GetIntValueFromNode(dbVal);
          if (i!=-1 || strcasecmp(sVar, "Content-Length")==0)
            GWEN_Buffer_AppendArgs(buf, "%s: %d\r\n", sVar, i);
        } /* if int */
        else {
          DBG_INFO(AQH_LOGDOMAIN, "Variable type %d of var [%s] not supported, ignoring", vtype, sVar);
        }
      } /* if sVar */
    }
    dbVar=GWEN_DB_GetNextVar(dbVar);
  }

  /* finalize header */
  GWEN_Buffer_AppendString(buf, "\r\n");
}



GWEN_MSG *AQH_HttpService_CreateResponseMsg(AQH_SERVICE *sv, int code, const char *text, const char *protocol, const char *page)
{
  GWEN_BUFFER *buf;
  GWEN_MSG *msg;

  buf=GWEN_Buffer_new(0, 256, 0, 1);
  AQH_HttpService_AddStatusLine(sv, code, text, protocol, buf);
  _writeDefaultResponseHeaderToBuffer(sv, (page && *page)?strlen(page):0, buf);

  if (page && *page)
    GWEN_Buffer_AppendString(buf, page);

  msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
  GWEN_Buffer_free(buf);
  return msg;
}



GWEN_MSG *AQH_HttpService_CreateRedirectingResponseMsg(AQH_SERVICE *sv, const char *protocol, const char *newPage)
{
  GWEN_BUFFER *buf;
  GWEN_MSG *msg;
  GWEN_DB_NODE *db;

  buf=GWEN_Buffer_new(0, 256, 0, 1);
  AQH_HttpService_AddStatusLine(sv, 303, "OK, redirecting to content", protocol, buf);
  db=GWEN_DB_Group_new("header");
  GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive");
  GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", "text/html; charset=utf-8");
  GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Location", newPage);
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", 0);
  AQH_HttpService_AddHeader(sv, db, buf);
  GWEN_DB_Group_free(db);

  msg=GWEN_Msg_fromBytes((const uint8_t*)GWEN_Buffer_GetStart(buf), GWEN_Buffer_GetUsedBytes(buf));
  GWEN_Buffer_free(buf);
  return msg;
}



int AQH_HttpService_AddFile(AQH_SERVICE *sv, GWEN_UNUSED AQH_SESSION *session, const char *fname, GWEN_BUFFER *buf)
{
  if (fname && *fname) {
    int rv;
    GWEN_BUFFER *nameBuf;

    nameBuf=_getPageFilePath(sv, NULL, fname);

    rv=GWEN_SyncIo_Helper_ReadFile(GWEN_Buffer_GetStart(nameBuf), buf);
    if (rv<0) {
      DBG_ERROR(AQH_LOGDOMAIN, "Error reading file \"%s\": %d", fname, rv);
      GWEN_Buffer_free(nameBuf);
      return rv;
    }
    GWEN_Buffer_free(nameBuf);
  }

  return 0;
}



void AQH_HttpService_SetupModuleAndPerms(AQH_SERVICE *sv, AQH_HTTP_REQUEST *rq, const char *modulName)
{
  AQH_SESSION *session;
  AQH_MODULE *m;

  AQH_HttpRequest_SetModuleName(rq, modulName);
  m=AQH_HttpService_GetModule(sv, modulName);
  if (m==NULL) {
    DBG_ERROR(NULL, "Module \"%s\" not found", modulName);
    AQH_HttpRequest_SetModulePerms(rq, 0);
  }
  else {
    AQH_HttpRequest_SetModule(rq, m);
  }

  session=AQH_HttpRequest_GetSession(rq);
  if (session) {
    AQH_USER *u;

    u=AQH_Session_GetUser(session);
    if (u) {
      if (strcasecmp(AQH_User_GetAlias(u), AQH_HTTP_SERVICE_ADMINUSER)==0) {
        DBG_ERROR(NULL, "special admin user, full access granted");
        AQH_HttpRequest_SetModulePerms(rq, 0xffffffff);
        return;
      }
    }
  }
  AQH_HttpService_SetRequestPerms(sv, rq);
}



GWEN_BUFFER *_getPageFilePath(const AQH_SERVICE *sv, const char *langFolder, const char *fileName)
{
  const char *sourceFolder;

  sourceFolder=AQH_HttpService_GetSourceFolder(sv);
  if (!(sourceFolder && *sourceFolder)) {
    DBG_ERROR(AQH_LOGDOMAIN, "No source folder given");
    return NULL;
  }
  else {
    GWEN_BUFFER *nameBuf;
  
    nameBuf=GWEN_Buffer_new(0, 256, 0, 1);
    GWEN_Buffer_AppendString(nameBuf, sourceFolder);
    GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S);
    if (langFolder && *langFolder) {
      GWEN_Buffer_AppendString(nameBuf, langFolder);
      GWEN_Buffer_AppendString(nameBuf, GWEN_DIR_SEPARATOR_S);
    }
    GWEN_Text_EscapeToBufferTolerant(fileName, nameBuf);
    return nameBuf;
  }
}






void _writeDefaultResponseHeaderToBuffer(AQH_SERVICE *sv, int contentLength, GWEN_BUFFER *buf)
{
  GWEN_DB_NODE *db;

  db=GWEN_DB_Group_new("header");
  GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Connection", "Keep-Alive");
  GWEN_DB_SetCharValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Type", "text/html; charset=utf-8");
  GWEN_DB_SetIntValue(db, GWEN_DB_FLAGS_OVERWRITE_VARS, "Content-Length", contentLength);

  AQH_HttpService_AddHeader(sv, db, buf);
  GWEN_DB_Group_free(db);
}



AQH_HTTP_URLHANDLER *_findMatchingUrlHandler(AQH_SERVICE *sv, const char *path)
{
  AQH_HTTP_SERVICE *xsv;
  AQH_HTTP_URLHANDLER *uh;

  xsv=GWEN_INHERIT_GETDATA(AQH_SERVICE, AQH_HTTP_SERVICE, sv);
  if (xsv==NULL) {
    DBG_ERROR(AQH_LOGDOMAIN, "Not a AQH_HttpService object");
    return NULL;
  }

  uh=AQH_HttpUrlHandler_List_First(xsv->urlHandlerList);
  while(uh) {
    if (AQH_HttpUrlHandler_UrlMatches(uh, path)) {
      DBG_INFO(AQH_LOGDOMAIN, "Found matching url handler for \"%s\"", path);
      return uh;
    }
    uh=AQH_HttpUrlHandler_List_Next(uh);
  }
  DBG_INFO(AQH_LOGDOMAIN, "No matching url handler for \"%s\"", path);
  return NULL;
}



