/*******************************************************************************
Copyright (c) 2014, Jan Koester jan.koester@gmx.net
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of the <organization> nor the
      names of its contributors may be used to endorse or promote products
      derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/

#include <stddef.h>
#include <sys/types.h>

#include <vector>
#include <string>
#include <memory>
#include <deque>
#include <map>

#include <netplus/socket.h>
#include <netplus/connection.h>
#include <netplus/eventapi.h>
#include <netplus/crypto/tls.h>

#include "config.h"

#include "httpdefinitions.h"
#include "hpack.h"

#pragma once

namespace libhttppp::qpack { struct HeaderField; }

namespace libhttppp {
  class HttpRequest;

  class HttpUrl {
  public:
    enum HttpProtocol{
        HTTP=0,
        HTTPS=1,
        HTTP3=2
    };

     HttpUrl();
     HttpUrl(const std::string &url,bool http3=false);
     HttpUrl(const HttpUrl &src);
     HttpUrl& operator=(const HttpUrl &src) = default;
     ~HttpUrl();

     bool operator==(const HttpUrl& other) const;
     bool operator<(const HttpUrl& other) const;

     int getProtocol() const;
     const std::string &getHost() const;
     int getPort() const;
     const std::string &getPath() const;

     void clear();

     std::string print() const;

  private:
      int            _protocol;
      std::string _host;
      int             _port;
      std::string _path;
  };

  class HttpResponse;

  class HttpClient{
  public:
      HttpClient( const HttpUrl &desturl, int vers = 2);
      ~HttpClient()=default;
      void reconnect();
      void setTimeout(int timeout_sec);
      const std::vector<char> Get(HttpRequest &nreq, size_t maxTries=0);
      const std::vector<char> Post(HttpRequest &nreq,const std::vector<char> &post, size_t maxTries=0);
      const std::vector<char> Put(HttpRequest &nreq,const std::vector<char> &put, size_t maxTries=0);
      const std::vector<char> Delete(HttpRequest &nreq, size_t maxTries=0);

      // Streaming API: send request, return parsed response headers only.
      // After this call, use readBodyChunk() to read body data incrementally.
      HttpResponse GetStream(HttpRequest &nreq);

      // Read the next chunk of body data (up to bufsize bytes).
      // Returns number of bytes written to buf, 0 when body is complete.
      size_t readBodyChunk(char *buf, size_t bufsize);

      // Non-blocking variant: returns 0 immediately if no data available yet.
      // Returns (size_t)-1 when stream is complete (no more data will come).
      size_t readBodyChunkNonBlocking(char *buf, size_t bufsize);

      // True while a streaming read is in progress.
      bool isStreaming() const;

      // Wait until upstream socket has data to read (or timeout expires).
      // timeout_ms: -1 = infinite, 0 = return immediately, >0 = milliseconds
      // Returns true if readable, false on timeout.
      bool waitReadable(int timeout_ms);

      // Shared TLS session cache — enables abbreviated TLS 1.2 handshakes
      // across reconnects to the same host.  One cache per process.
      static netplus::TlsSessionCache& tlsSessionCache();
  private:
      void resetConnection();
      void _ensureConnected();
      bool tryHttp3First();
      int readchunk(const char *data,int datasize,int &pos);

      // Non-blocking I/O helpers using poll() for efficient waiting
      size_t _recvBlocking(netplus::buffer &b, int timeout_sec = 0);
      // Returns 0 on EAGAIN (no data yet), otherwise bytes read
      size_t _recvNonBlocking(netplus::buffer &b);
      void _sendAll(const char *data, size_t len);
      void _sendAll(const std::string &data);

      // Shared HTTP/1.x response reader (avoids code duplication)
      std::vector<char> _h1ReadResponse(const std::string &label);

      // HTTP/2 client helpers
      bool _isH2 = false;
      bool _h2PrefaceSent = false;       // true after connection preface sent

      // HTTP/3 client helpers
      uint32_t _h2NextStreamId = 1;     // next client-initiated stream ID (odd)
      std::unique_ptr<hpack::Decoder> _h2Decoder; // persistent HPACK decoder for connection
      const std::vector<char> _h2Request(const std::string &method,
                                          HttpRequest &nreq,
                                          const std::vector<char> *postBody = nullptr);
      const std::vector<char> _h3Request(const std::string &method,
                  HttpRequest &nreq,
                  const std::vector<char> *postBody = nullptr);

      // Streaming state
      enum StreamMode { STREAM_NONE, STREAM_CONTENT_LENGTH, STREAM_CHUNKED, STREAM_EOF,
                        STREAM_H2, STREAM_H3 };
      StreamMode _streamMode = STREAM_NONE;
      size_t _streamRemaining = 0;        // bytes left for content-length mode
      std::vector<char> _streamBuf;       // leftover data from header read
      size_t _streamBufPos = 0;
      // Chunked streaming sub-state
      bool _streamChunkDone = false;      // true after final 0-length chunk
      size_t _streamChunkRemaining = 0;   // bytes left in current chunk

      // HTTP/2 streaming state
      uint32_t _streamH2Sid = 0;          // stream ID for active H2 stream
      bool     _streamH2EndStream = false;
      std::vector<uint8_t> _streamH2Raw; // raw frame buffer

      // HTTP/3 streaming state
      uint64_t _streamH3Sid = 0;          // stream ID for active H3 stream
      bool     _streamH3EndStream = false;
      std::vector<uint8_t> _streamH3Raw; // raw frame buffer
      std::vector<char> _streamH3Body;   // decoded DATA frames not yet consumed
      size_t   _streamH3BodyPos = 0;        // consumed offset into _streamH3Body
      bool     _streamH3InDataFrame = false; // true when inside a partial DATA frame
      uint64_t _streamH3DataRemaining = 0;  // bytes left in current DATA frame

  private:
      HttpUrl _url;
      std::unique_ptr<netplus::socket> _cltsock;
      netplus::socketwait _sw;
      netplus::x509cert _cert;
      int _recvTimeoutSec = 60;
      int _sendTimeoutSec = 30;
  };


  class HttpHeader {
  public:
    class HeaderData{
    public:
    class Values{
      public:
        Values &operator=(const std::string &val);
        Values &operator=(size_t val);
        Values &operator=(int val);
        Values& operator=(const Values &val);

        Values &operator<<(const std::string &value);
        Values &operator<<(size_t value);
        Values &operator<<(int value);

        const std::string &getvalue();
        int          getIntvalue();
        size_t       getSizetValue();

        Values      *nextvalue();
        Values(const std::string& val);
        Values(const Values& val);
        Values()=default;
        ~Values() = default;
      private:
        std::string  _value;
        std::unique_ptr<Values> _nextvalue=nullptr;
        friend class HeaderData;
      };

      Values *getfirstValue();
      Values &at(int pos);
      Values &operator[](int pos);

      void push_back(const Values &val);
      void push_back(const std::string &val);
      void push_back(const char* val);
      void push_back(size_t val);
      void push_back(int val);

      bool empty();

      void erase(int pos);

      void clear();

      const std ::string &getkey();

      HeaderData *nextHeaderData();
      HeaderData(const std ::string &key);
      ~HeaderData() = default;
    private:
      std::string  _Key;
      std::unique_ptr<Values> _firstValue=nullptr;
      Values      *_lastValue=nullptr;
      std::unique_ptr<HeaderData>  _nextHeaderData=nullptr;
      friend class HttpHeader;
    };
    
    HeaderData         *getfirstHeaderData();
    HeaderData         *getHeaderData(const std ::string &key) const;
    HeaderData         *setHeaderData(const std ::string &key);

    void             deldata(const std ::string &key);
    void             deldata(HeaderData*pos);
    
    size_t      getElements();
    size_t      getHeaderSize();

    void        clear();
  protected:
    HttpHeader();
    virtual ~HttpHeader()=default;
    std::unique_ptr<HeaderData> _firstHeaderData;
    HeaderData *_lastHeaderData;
  };
  
  class HttpResponse : public HttpHeader,public netplus::con {
  public:
    HttpResponse();
    HttpResponse(const HttpResponse &src);
    ~HttpResponse();

    /*server methods*/
    void   setState(const std ::string &httpstate);
    void   setContentType(const std ::string &type);
    void   setContentLength(size_t len);
    void   setConnection(const std ::string &type);
    void   setTransferEncoding(const std ::string &enc);

    /*client methods*/
    const std ::string &getState() const;
    int                getStatusCode() const;
    const std ::string &getContentType() const;
    size_t              getContentLength() const;
    const std ::string &getConnection() const;
    const std ::string &getVersion() const;
    HttpHeader::HeaderData::Values *getTransferEncoding() const;

    size_t printHeader(std::vector<char> &buffer);

    /*server methods*/
    void send(netplus::con &curconnection,const std::string &data,int datalen=0); //only use as server
    void send(netplus::con &curconnection,const unsigned  char *data,int datalen); //only use as server
    void send(netplus::con &curconnection,const std::vector<char> &data,int datalen=0); //only use as server

    /*client method*/
    size_t   parse(const char *in,size_t inlen);

  private:
    bool _storeResponseInfo(netplus::con &curconnection, int datalen);

    std::string      _State=HTTP200;
    std::string      _Version;
    int              _StatusCode=200;
    HeaderData      *_TransferEncoding;
    HeaderData      *_Connection;
    HeaderData      *_ContentType;
    HeaderData      *_ContentLength;
    mutable std::string _ContentTypeCache;
  };
  
 
  class HttpRequest : public HttpHeader, public netplus::con{
  public:
    HttpRequest();
    HttpRequest(netplus::eventapi *evapi);
    ~HttpRequest();

    void clear();

    /*server methods*/

    size_t                    parse(); //only use as server

    /*protocol-specific parse helpers (all store into _firstHeaderData)*/
    size_t parseH2(const std::vector<hpack::HeaderField> &headers, uint32_t stream_id = 0);
    size_t parseH3(const std::vector<qpack::HeaderField> &headers);

    void                      printHeader(std::string &buffer);
    int                         getRequestType();
    const std::string &getRequestURL();
    const std::string &getRequest();
    size_t                    getRequestLength();
    const std::string &getRequestVersion();
    const std::string &getHost();
    size_t                    getContentLength();
    size_t                    getMaxUploadSize();

    /*mobilphone switch*/
    bool isMobile();

    /*Client methods*/
    void              setRequestType(int req);
    void              setRequestURL(const std::string &url);
    void              setRequestVersion(const std::string &version);
    /*only for post Reuquesttype*/
    void              setRequestData(const std::string &data,size_t len);
    void              setMaxUploadSize(size_t upsize);

    void              send(const HttpUrl &dest,std::unique_ptr<netplus::socket> &sock);

  private:
    size_t            parseH1(); // HTTP/1.x request parsing

    /*
     * Helper: extracts URL path from :path header (strips query string).
     * Used by getRequestURL() and parse helpers.
     */
    static std::string extractPath(const std::string &target);

    int               _RequestType = PARSEREQUEST;
    size_t            _MaxUploadSize = DEFAULT_UPLOADSIZE;

    // Cached strings derived from _firstHeaderData pseudo-headers.
    // Populated by parseH1/parseH2/parseH3, read by getters.
    mutable std::string _cachedRequestURL;
    mutable std::string _cachedRequest;
    mutable std::string _cachedRequestVersion;
    mutable std::string _cachedHost;

    // HTTP/2 and HTTP/3 protocol state (managed by HttpEvent).
    // All H2-specific mutable state lives in a heap-allocated struct so
    // that inline-layout corruption of HttpRequest cannot trash the
    // deque / map / decoder internals.
    int               _httpProtocol = 0;  // 0=HTTP/1.x, 1=HTTP/2, 2=HTTP/3

    struct H2PendingResponse {
      uint32_t    streamId;
      std::string body;       // remaining body data to send as DATA frames
      size_t      offset = 0; // how far into body we've sent
    };

    // Active streaming response state — lives on the connection's H2State
    // so Http2RequestEvent can resume sending after WINDOW_UPDATE.
    struct H2StreamingResponse {
      uint32_t    streamId = 0;
      size_t      contentLength = 0;
      size_t      totalSent = 0;
      std::string pendingData;     // buffered DATA not yet framed
      size_t      pendingOffset = 0;
      std::unique_ptr<HttpRequest> tempreq; // per-stream request for ResponseEvent
      int         tid = 0;
      ULONG_PTR   args = 0;
      size_t      emptyCount = 0;
      unsigned int backoffMs = 1;
      bool        finished = false;
    };

    struct H2PendingIncoming {
      std::vector<hpack::HeaderField> headers;
      std::string body;
      std::vector<uint8_t> rawHpack;      // accumulates HPACK across CONTINUATION frames
      bool headersComplete = false;       // true once END_HEADERS received
      bool endStreamOnHeaders = false;    // END_STREAM was on HEADERS frame
      bool streaming = false;             // body handled by onH2DataChunk callback
    };

    struct H2State {
      uint32_t          streamId = 0;
      bool              headersSent = false;
      bool              serverPrefaceSent = false;
      size_t            expectedContentLength = 0;
      size_t            bodyBytesSent = 0;
      std::deque<H2PendingResponse>            pendingResponses;
      std::map<uint32_t, H2PendingIncoming>    pendingIncoming;
      hpack::Decoder                           hpackDecoder;
      // Peer flow-control windows (RFC 7540 §6.9)
      int32_t peerConnWindow = 65535;     // connection-level
      int32_t peerInitialStreamWindow = 65535; // from peer SETTINGS
      size_t  peerMaxFrameSize = 16384;        // from peer SETTINGS_MAX_FRAME_SIZE (0x05)
      std::map<uint32_t, int32_t> peerStreamWindows; // per-stream
      // Active streaming responses (one per stream)
      std::map<uint32_t, std::shared_ptr<H2StreamingResponse>> activeStreams;
    };

    // Lazily allocated when the connection is upgraded to HTTP/2.
    std::unique_ptr<H2State> _h2;

    // Allocate H2 state if not yet present; return reference.
    H2State &h2state() {
      if (!_h2) _h2 = std::make_unique<H2State>();
      return *_h2;
    }

    friend class HttpForm;
    friend class HttpResponse;
    friend class HttpEvent;
  };
  
  class HttpForm {
  public:
    // ─── Multipart form-data (RFC 2046) ───────────────────
    struct MultipartEntry {
      struct Header {
        std::string key;    // lowercased header name (e.g. "content-disposition")
        std::string value;  // full header value
      };

      struct Disposition {
        std::string key;    // e.g. "name", "filename"
        std::string value;  // e.g. "field1", "upload.txt"
      };

      std::vector<Header>      headers;
      std::vector<Disposition> dispositions;
      std::vector<char>        value;  // raw body (binary-safe for file uploads)
    };

    // ─── URL-encoded form data ────────────────────────────
    struct UrlEntry {
      std::string key;
      std::string value;
    };

    HttpForm() = default;
    ~HttpForm() = default;

    void parse(HttpRequest &request);

    // Accessors
    const std::string                  &getContentType() const { return _contentType; }
    const std::string                  &getBoundary()    const { return _boundary; }
    const std::vector<MultipartEntry>  &multipartData()  const { return _multipartEntries; }
    const std::vector<UrlEntry>        &urlData()        const { return _urlEntries; }

    // URL encoding / decoding utilities
    static void urlDecode(const std::string &in, std::string &out);
    static void urlEncode(const std::string &in, std::string &out);

  private:
    void _parseMultipart(const char *data, size_t len);
    void _parseMultiSection(const char *data, size_t len, size_t start, size_t end);
    void _parseUrlDecode(const char *data, size_t len);

    std::string                  _boundary;
    std::string                  _contentType;
    std::vector<MultipartEntry>  _multipartEntries;
    std::vector<UrlEntry>        _urlEntries;
  };

  class HttpCookie {
  public:
    class CookieData {
    public:
      CookieData *nextCookieData() const;
      const std::string &getKey() const;
      const std::string &getValue() const;
      CookieData()=default;
	  CookieData(const CookieData& src);
      ~CookieData();
    private:
      std::string _Key;
      std::string _Value;
      std::unique_ptr <CookieData> _nextCookieData=nullptr;

      friend class HttpCookie;
    };
    HttpCookie();
    ~HttpCookie();
    void parse(libhttppp::HttpRequest& curreq);
    void setcookie(libhttppp::HttpResponse& curresp,
                   const std::string &key,const std::string &value,
                   const std::string &comment="",const std::string &domain="",
                   int maxage=-1,const std::string &path="",
                   bool secure=false,const std::string &version="1",const std::string &samesite="",bool httponly=false);
    CookieData    *getfirstCookieData();
    CookieData    *getlastCookieData();
    CookieData    *addCookieData();
  private:
    std::unique_ptr <CookieData> _firstCookieData;
    CookieData     *_lastCookieData;
  };

#define BASICAUTH  0
#define DIGESTAUTH 1
#define NTLMAUTH   2
  
  class HttpAuth {
  public:
    HttpAuth();
    ~HttpAuth();
    void        parse(libhttppp::HttpRequest &curreq);
    void        setAuth(libhttppp::HttpResponse &curresp);
    
    void        setAuthType(int authtype);
    void        setRealm(const std::string &realm);
    void        setUsername(const std::string &username);
    void        setPassword(const std::string &password);
    
    const std::string &getUsername();
    const std::string &getPassword();
    int         getAuthType();
    const std::string &getAuthRequest();
    
  private:
    int             _Authtype;
    std::string _Username;
    std::string _Password;
    std::string _Realm;
    std::string _Nonce;
    
  };
};
