/****************************************************************************
 * This file is part of the project AqHome.
 * AqHome (c) by 2025 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


#include "./mroot_p.h"

#include "aqhome-cgi/modules/devices/mdevices.h"
#include "aqhome-cgi/modules/common/madmin.h"

#include <aqcgi/service/module.h>

#include <gwenhywfar/debug.h>
#include <gwenhywfar/timestamp.h>
#include <gwenhywfar/text.h>



/* ------------------------------------------------------------------------------------------------
 * defs and enums
 * ------------------------------------------------------------------------------------------------
 */

#define AQH_SERVICE_SESSION_MAXAGE 86400



/* ------------------------------------------------------------------------------------------------
 * global vars
 * ------------------------------------------------------------------------------------------------
 */

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

static AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sModuleName);
static int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, AQCGI_SESSION *session, const char *sLastPathElem);
static int _handleRqIndex(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf);
static int _handleRqLogin(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf);
static int _handleRqLoginGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf);
static int _handleRqLoginPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq);
static AQCGI_USER *_getAndCheckUser(AQCGI_MODULE *m, AQCGI_REQUEST *rq);


/* ------------------------------------------------------------------------------------------------
 * code
 * ------------------------------------------------------------------------------------------------
 */

AQCGI_MODULE *AQH_ModRoot_new(AQCGI_SERVICE *sv, const char *baseFolder)
{
  AQCGI_MODULE *m;

  m=AQCGI_Module_new();
  AQH_ModService_Extend(m, sv, baseFolder);
  AQH_ModService_SetHandleRequestFn(m, _handleRequest);
  AQH_ModService_SetLoadSubModuleFn(m, _loadSubModule);

  return m;
}



AQCGI_MODULE *_loadSubModule(AQCGI_MODULE *m, GWEN_UNUSED AQCGI_REQUEST *rq, GWEN_UNUSED AQCGI_SESSION *session, const char *sModuleName)
{
  AQCGI_SERVICE *sv;

  sv=AQH_ModService_GetService(m);
  if (strcasecmp(sModuleName, "devices")==0) {
    AQCGI_MODULE *mSub;

    mSub=AQCGI_Service_LoadModule(sv, sModuleName);
    if (mSub) {
      const char *s;
      GWEN_BUFFER *nbuf;

      nbuf=GWEN_Buffer_new(0, 256, 0, 1);
      s=AQH_ModService_GetBaseFolder(m);
      GWEN_Buffer_AppendArgs(nbuf, "%s/devices", s?s:".");

      AQH_ModDevices_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf));
      AQCGI_Module_Tree2_AddChild(m, mSub);
      GWEN_Buffer_free(nbuf);
      return mSub;
    }
  }
  else if (strcasecmp(sModuleName, "admin")==0) {
    AQCGI_MODULE *mSub;

    mSub=AQCGI_Service_LoadModule(sv, sModuleName);
    if (mSub) {
      const char *s;
      GWEN_BUFFER *nbuf;

      nbuf=GWEN_Buffer_new(0, 256, 0, 1);
      s=AQH_ModService_GetBaseFolder(m);
      GWEN_Buffer_AppendArgs(nbuf, "%s/admin", s?s:".");

      AQH_ModAdmin_Extend(mSub, AQH_ModService_GetService(m), GWEN_Buffer_GetStart(nbuf));
      AQCGI_Module_Tree2_AddChild(m, mSub);
      GWEN_Buffer_free(nbuf);
      return mSub;
    }
  }
  return NULL;
}



int _handleRequest(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_UNUSED AQCGI_SESSION *session, const char *sLastPathElem)
{
  GWEN_BUFFER *dbuf;

  dbuf=GWEN_Buffer_new(0, 256, 0, 1);
  AQH_ModService_AddHeader(m, "en", dbuf);

  if (strcasecmp(sLastPathElem, "login")==0)
    _handleRqLogin(m, rq, dbuf);
  else if (strcasecmp(sLastPathElem, "signup")==0) {
    AQCGI_Request_SetResponseCode(rq, 501);
    AQCGI_Request_SetResponseText(rq, "Not Implemented");
  }
  else if (strcasecmp(sLastPathElem, "confirm")==0) {
    AQCGI_Request_SetResponseCode(rq, 501);
    AQCGI_Request_SetResponseText(rq, "Not Implemented");
  }
  else if (strcasecmp(sLastPathElem, "index.html")==0)
    _handleRqIndex(m, rq, dbuf);
  else {
    AQCGI_Request_SetResponseCode(rq, 404);
    AQCGI_Request_SetResponseText(rq, "Not Found");
  }

  AQH_ModService_AddFooter(m, "en", dbuf);
  AQCGI_Request_SetBufferResponseBody(rq, dbuf);
  AQCGI_Request_AddResponseHeaderData(rq, "Content-type: text/html");

  return AQCGI_SendResponse(rq);
}



int _handleRqIndex(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf)
{
  if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_GET)
    return AQH_ModService_RespondWithFile(m, rq, "en", "index.html", dbuf);
  else {
    DBG_ERROR(NULL, "Invalid request method %d", AQCGI_Request_GetRequestMethod(rq));
    AQCGI_Request_SetResponseCode(rq, 405);
    AQCGI_Request_SetResponseText(rq, "Method Not Allowed");
    return GWEN_ERROR_GENERIC;
  }
}



int _handleRqLogin(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf)
{
  if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_GET)
    _handleRqLoginGet(m, rq, dbuf);
  else if (AQCGI_Request_GetRequestMethod(rq)==AQCGI_REQUEST_METHOD_POST)
    _handleRqLoginPost(m, rq);
  else {
    DBG_ERROR(NULL, "Invalid request method %d", AQCGI_Request_GetRequestMethod(rq));
    AQCGI_Request_SetResponseCode(rq, 405);
    AQCGI_Request_SetResponseText(rq, "Method Not Allowed");
    return GWEN_ERROR_GENERIC;
  }
  return 0;
}



int _handleRqLoginGet(AQCGI_MODULE *m, AQCGI_REQUEST *rq, GWEN_BUFFER *dbuf)
{
  GWEN_DB_NODE *dbQuery;
  const char *sSrc;
  GWEN_DB_NODE *dbVars;
  int rv;

  dbVars=GWEN_DB_Group_new("vars");
  dbQuery=AQCGI_Request_GetDbQuery(rq);
  sSrc=dbQuery?GWEN_DB_GetCharValue(dbQuery, "src", 0, NULL):NULL;
  if (sSrc && *sSrc)
    GWEN_DB_SetCharValue(dbVars, 0, "destination", sSrc);

  rv=AQH_ModService_RespondWithVarFile(m, rq, "en", "login.html", dbVars, dbuf);
  GWEN_DB_Group_free(dbVars);
  return rv;
}



int _handleRqLoginPost(AQCGI_MODULE *m, AQCGI_REQUEST *rq)
{
  AQCGI_SERVICE *sv;
  AQCGI_USER *user;
  AQCGI_SESSION *session;
  GWEN_BUFFER *tbuf;
  GWEN_TIMESTAMP *ts;
  GWEN_DB_NODE *dbPost;
  const char *s;
  int rv;

  DBG_ERROR(NULL, "Handling request");
  sv=AQH_ModService_GetService(m);
  user=_getAndCheckUser(m, rq);
  if (user==NULL) {
    DBG_INFO(NULL, "here");
    return GWEN_ERROR_GENERIC;
  }

  ts=GWEN_Timestamp_NowInLocalTime();
  AQCGI_User_SetTimestampLastLogin(user, ts);
  rv=AQCGI_Service_SaveUser(sv, user);
  if (rv<0) {
    DBG_ERROR(NULL, "Error saving user \"%s\"", AQCGI_User_GetAlias(user));
    AQCGI_Request_SetResponseCode(rq, 500);
    AQCGI_Request_SetResponseText(rq, "Internal Error");
    AQCGI_User_free(user);
    return rv;
  }

  /* generate session */
  tbuf=GWEN_Buffer_new(0, 64, 0, 1);
  AQCGI_GenerateSessionId(tbuf);
  session=AQCGI_Session_new();
  AQCGI_Session_SetTimestampCreation(session, ts);
  AQCGI_Session_SetTimestampLastAccess(session, ts);
  AQCGI_Session_SetUid(session, GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_free(tbuf);
  AQCGI_Session_SetUserAlias(session, AQCGI_User_GetAlias(user));
  rv=AQCGI_Service_AddSession(sv, session);
  if (rv<0) {
    DBG_ERROR(NULL, "Error adding session for user \"%s\" (%d)", AQCGI_User_GetAlias(user), rv);
    AQCGI_Request_SetResponseCode(rq, 500);
    AQCGI_Request_SetResponseText(rq, "Internal Error");
    AQCGI_Session_free(session);
    AQCGI_User_free(user);
    return GWEN_ERROR_INTERNAL;
  }

  /* add Set-Cookie header */
  tbuf=GWEN_Buffer_new(0, 256, 0, 1);
  GWEN_Buffer_AppendArgs(tbuf, "Set-Cookie: session=%s; max-age=%d", AQCGI_Session_GetUid(session), AQH_SERVICE_SESSION_MAXAGE);
  AQCGI_Request_AddResponseHeaderData(rq, GWEN_Buffer_GetStart(tbuf));
  GWEN_Buffer_free(tbuf);

  /* finish */
  dbPost=AQCGI_Request_GetDbPostBody(rq);
  s=dbPost?GWEN_DB_GetCharValue(dbPost, "dest", 0, NULL):NULL;
  if (s && *s) {
    GWEN_BUFFER *ubuf;

    ubuf=GWEN_Buffer_new(0, 128, 0, 1);
    if (GWEN_Text_UnescapeToBufferTolerant(s, ubuf)>=0) {
      const char *p;

      p=GWEN_Buffer_GetStart(ubuf);
      if (*p=='/')
        AQH_ModService_Redirect(rq, p);
      else {
        DBG_ERROR(NULL, "Destination does not start with \"/\", not using it [%s]", p);
        AQCGI_Request_AddResponseHeaderData(rq, "Location: index.html");
        AQCGI_Request_SetResponseCode(rq, 303);
        AQCGI_Request_SetResponseText(rq, "See other");
      }
    }
    GWEN_Buffer_free(ubuf);
  }
  else {
    AQCGI_Request_AddResponseHeaderData(rq, "Location: index.html");
    AQCGI_Request_SetResponseCode(rq, 303);
    AQCGI_Request_SetResponseText(rq, "See other");
  }

  AQCGI_Session_free(session);
  AQCGI_User_free(user);

  return 0;
}



AQCGI_USER *_getAndCheckUser(AQCGI_MODULE *m, AQCGI_REQUEST *rq)
{
  GWEN_DB_NODE *dbPost;

  dbPost=AQCGI_Request_GetDbPostBody(rq);
  if (dbPost) {
    AQCGI_SERVICE *sv;
    const char *sUserName;
    const char *sPasswd;
    AQCGI_USER *user;
    const char *hashedPaswd;
    GWEN_BUFFER *buf;

    sv=AQH_ModService_GetService(m);
    sUserName=GWEN_DB_GetCharValue(dbPost, "userid", 0, NULL);
    sPasswd=GWEN_DB_GetCharValue(dbPost, "password", 0, NULL);
    if (!(sUserName && *sUserName && sPasswd && *sPasswd)) {
      DBG_ERROR(NULL, "Either user name or password missing");
      AQCGI_Request_SetResponseCode(rq, 400);
      AQCGI_Request_SetResponseText(rq, "Bad Request");
      return NULL;
    }

    DBG_ERROR(NULL, "Loading user \"%s\" (%p)", sUserName, sv);
    user=AQCGI_Service_LoadUser(sv, sUserName);
    if (user==NULL) {
      DBG_ERROR(NULL, "User \"%s\" not found", sUserName);
      AQCGI_Request_SetResponseCode(rq, 403);
      AQCGI_Request_SetResponseText(rq, "Forbidden");
      return NULL;
    }
    DBG_ERROR(NULL, "Loaded user \"%s\"", sUserName);

    if (AQCGI_User_GetState(user)!=AQCGI_UserState_Active) {
      DBG_ERROR(NULL, "User \"%s\" not active", sUserName);
      AQCGI_Request_SetResponseCode(rq, 403);
      AQCGI_Request_SetResponseText(rq, "Forbidden");
      AQCGI_User_free(user);
      return NULL;
    }

    hashedPaswd=AQCGI_User_GetHashedPassword(user);
    if (!(hashedPaswd && *hashedPaswd)) {
      DBG_ERROR(NULL, "User \"%s\" has no hashed password", sUserName);
      AQCGI_Request_SetResponseCode(rq, 403);
      AQCGI_Request_SetResponseText(rq, "Forbidden");
      AQCGI_User_free(user);
      return NULL;
    }
    buf=GWEN_Buffer_new(0, 256, 0, 1);
    AQCGI_HashMd256ToBuffer(sPasswd, buf);
    DBG_ERROR(NULL, "Password: [%s]", sPasswd); // TODO: Remove!!!
    DBG_ERROR(NULL, "Hashed password: [%s]", GWEN_Buffer_GetStart(buf));
    if (strcasecmp(GWEN_Buffer_GetStart(buf), hashedPaswd)!=0) {
      DBG_ERROR(NULL, "Bad password for user \"%s\"", sUserName);
      AQCGI_Request_SetResponseCode(rq, 403);
      AQCGI_Request_SetResponseText(rq, "Forbidden");
      GWEN_Buffer_free(buf);
      AQCGI_User_free(user);
      return NULL;
    }
    GWEN_Buffer_free(buf);

    DBG_ERROR(NULL, "User \"%s\" accepted", sUserName);
    return user;
  }
  else {
    DBG_ERROR(NULL, "No POST data");
    AQCGI_SendResponseWithStatus(rq, 400, "Bad Request");
    AQCGI_Request_SetResponseCode(rq, 400);
    AQCGI_Request_SetResponseText(rq, "Bad Request");
    return NULL;
  }
}







