#include "../include/ServerUtils.h"
#include "../include/WordUtils.h"
#include "../include/DataType.h"
#include "../include/FileUtils.h"
#include "../include/Options.h"

#include <string>
#include <utility>
#include <vector>
#include <iostream>
#include <unistd.h>
#include <netinet/in.h>
#include <thread>
#include <chrono>
#include <cstring>
#include <random>
#include <sys/socket.h>
#include <cstdint>

string css;
vector<string> images;
vector<unordered_map<string, unordered_map<string, int>>> all_lists;
shared_ptr<ConcurrentQueue<Track>> tracks;
Options options_;

void [[noreturn]] ServerUtils::serve(shared_ptr<ConcurrentQueue<Track>> cq_track, const Options& options) {
    options_ = options;
    tracks = std::move(cq_track);
    css = WordUtils::load_css(options_.css);
    images = FileUtils::get_image_list(options_.images);
    const vector<string> words = FileUtils::get_wordlists(options_.word_lists);

    for (const auto& word : words) {
        all_lists.push_back(WordUtils::load_data(word));
    }

    // server_fd is a file descriptor.
    const int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    
    sockaddr_in addr{};
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_fd, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) != 0) {
        cout << "Please wait for the pipe to close.";
        return;
    }

    listen(server_fd, 50);
    
    cout << "Server is running on http://localhost:8888 \n";
    
    while (true) {
        int client_fd = accept(server_fd, nullptr, nullptr);

        if (client_fd == -1) continue;

        thread(process_request, client_fd).detach();
    }
    
    close(server_fd);
}

void ServerUtils::process_request(const int client_fd) {
    const uint32_t ip = get_ip(client_fd);

    char buffer[1024];
    const unsigned long bytes_received = recv(client_fd, buffer, sizeof(buffer) - 1, 0);

    string url;
    string user_agent;

    if (bytes_received > 0) {
        buffer[bytes_received] = '\0';

        url = WordUtils::extract_url(string(buffer));
        user_agent = WordUtils::extract_user_agent(string(buffer));

        if (url.empty()) {
            close(client_fd);
            cerr << "AAAA \n";
        }
    } else {
        close(client_fd);
        cerr << "BBBB \n";
    }

    if (url == "/style.css") [[unlikely]] {
        send_css(client_fd);
    }

    else if (url == "/favicon.png") [[unlikely]] {
        send_image(client_fd, options_.favicon, PNG);
    }

    else if (WordUtils::contains_image(url)) [[unlikely]] {
        string p = options_.images;
        p += WordUtils::extract_image_name(url);
        send_image(client_fd, p, AVIF);
    }

    else [[likely]] {
        Track track;
        track.Ip = ip;
        track.UserAgent = user_agent;

        tracks->push(track);

        const unsigned long hash3 = WordUtils::fnv1aHash(url);

        send_html(client_fd, hash3);
    }
    
    close(client_fd);
}

void ServerUtils::send_html(const int client_fd, const size_t hash) {
    const string hashes = to_string(hash);

    unsigned short itr = 0;
    const unsigned short end = hashes.size();

    minstd_rand generator(hash);
    uniform_int_distribution<unsigned short> distribution(0, end - 1);

    const int link = distribution(generator);
    distribution.param(uniform_int_distribution<unsigned short>::param_type(0, images.size() - 1));
    const int image = distribution(generator);
    distribution.param(uniform_int_distribution<unsigned short>::param_type(0, 8));
    const int l = distribution(generator);
    distribution.param(uniform_int_distribution<unsigned short>::param_type(0, end - 2));
    const int img = distribution(generator);

    string html = HTML_RESPONSE_HEADER;
    html += HTML_BEGINNING;
    html += '\n';
    html += HTML_NAV;
    html += '\n';
    html += HTML_MAIN_1;

    while (itr < end) {
        html += WordUtils::create_tag(all_lists[l], hashes[itr]);

        if (itr == link) {
            html += WordUtils::create_link(all_lists[l], hash);
        }

        if (itr == img) {
            html += WordUtils::create_image(images[image]);
        }

        itr++;
    }

    html += HTML_MAIN_2;
    html += HTML_FOOTER;
    html += HTML_END;

    send_data(client_fd, html);
}

void ServerUtils::send_css(const int client_fd) {
    send_data(client_fd, CSS_RESPONSE_HEADER + css);
}

void ServerUtils::send_image(const int client_fd, const string& path, const image_type type) {
    const vector<unsigned char> image = FileUtils::open_image(path);

    string s = "HTTP/1.1 200 OK\r\n";

    if (type == PNG) [[unlikely]] {
        s += "Content-Type: image/png\r\n";
    }

    if (type == AVIF) [[likely]] {
        s += "Content-Type: image/avif\r\n";
    }

    s += "Cache-control: max-age=12000\r\n";
    s += "Content-Length: ";
    s += to_string(image.size());
    s += "\r\n";
    s += "\r\n";

    send(client_fd, s.c_str(), s.size(), 0);

    send(client_fd, image.data(), image.size(), 0);
}
void ServerUtils::send_data(const int client_fd, const string& data) {
    send(client_fd, data.c_str(), data.size(), 0);
}

uint32_t ServerUtils::get_ip(const int client_fd) {
    sockaddr_storage addr{};
    socklen_t len = sizeof(addr);

    if (getpeername(client_fd, reinterpret_cast<sockaddr *>(&addr), &len) == -1) {
        perror("getpeername");
        return 0;
    }

    const sockaddr_in* s = reinterpret_cast<sockaddr_in *>(&addr);
    return ntohl(s->sin_addr.s_addr); // Convert network byte order to host byte order

}