#include "font_resolve.h"
#include "file.h"
#include "util.h"
#include "error.h"
#include "log.h"
#include "show.h"
#include "kutil.h"

#include <algorithm>
#include <regex>
#include <sstream>
#include <filesystem>

namespace fs = std::filesystem;


std::string name_to_dirname(std::string name)
{
    std::string result {};
    for (char c : name) {
        if (c == ' ')
            result += '-';
        else
            result += std::tolower(c);
    }
    return result;
}


std::string name_to_google_query(std::string name)
{
    std::string result {};
    for (char c : name) {
        if (c == ' ')
            result += '+';
        else
            result += c;
    }
    return result;
}


static void classify_from_css(Resolved_font& font, std::string css_path)
{
    // Parse the @font-face blocks in the CSS to determine variant → filename mapping
    std::string css = string_from_file(css_path);
    std::regex face_rgx(
        R"(@font-face\s*\{[^}]*font-style:\s*(\w+);[^}]*font-weight:\s*(\w+);[^}]*url\('([^']+\.ttf)'\)[^}]*\})",
        std::regex::multiline);
    auto begin = std::sregex_iterator(css.begin(), css.end(), face_rgx);
    auto end = std::sregex_iterator();
    for (auto it = begin; it != end; ++it) {
        std::string style = (*it)[1];
        std::string weight = (*it)[2];
        std::string url_path = (*it)[3];
        // URL is like 'dir-name/Filename.ttf' — extract just the filename
        std::string filename = url_path.substr(url_path.rfind('/') + 1);
        bool is_bold = (weight == "700" || weight == "bold");
        bool is_italic = (style == "italic" || style == "oblique");
        if (is_bold && is_italic)
            font.bold_italic = filename;
        else if (is_bold)
            font.bold = filename;
        else if (is_italic)
            font.italic = filename;
        else
            font.regular = filename;
    }
}

static Resolved_font resolve_bundled(std::string family_name, std::string dir_name)
{
    std::string bundled_dir = klammertext_dir() + "/sks/font/fonts/" + dir_name;
    std::string bundled_css = bundled_dir + ".css";
    if (fs::exists(bundled_dir) && fs::exists(bundled_css)) {
        Resolved_font font {};
        font.family_name = family_name;
        font.dir_name = dir_name;
        font.font_dir = bundled_dir;
        font.css_file = bundled_css;
        font.from_cache = false;
        classify_from_css(font, bundled_css);
        return font;
    }
    return {};
}


static Resolved_font resolve_cached(std::string family_name, std::string dir_name)
{
    std::string cache_base = cache_directory("_fonts");
    std::string cache_dir = cache_base + "/" + dir_name;
    std::string cache_css = cache_base + "/" + dir_name + ".css";
    if (fs::exists(cache_dir) && fs::exists(cache_css)) {
        Resolved_font font {};
        font.family_name = family_name;
        font.dir_name = dir_name;
        font.font_dir = cache_dir;
        font.css_file = cache_css;
        font.from_cache = true;
        classify_from_css(font, cache_css);
        return font;
    }
    return {};
}


static Resolved_font fetch_google_font(std::string family_name, std::string dir_name)
{
    (void)K::log(1, "Fetching font \"" + family_name + "\" from Google Fonts");

    std::string query = name_to_google_query(family_name);
    std::string url =
        "https://fonts.googleapis.com/css2?family=" + query +
        ":ital,wght@0,400;0,700;1,400;1,700&display=swap";

    std::string cmd = "curl -s -H 'User-Agent: Mozilla/4.0' '" + url + "'";
    std::string css_response = exec(cmd.c_str());

    if (css_response.empty() || css_response.find("@font-face") == std::string::npos) {
        return {};
    }

    // Parse @font-face blocks to extract style, weight, and .ttf URL
    struct Font_variant {
        std::string style;   // "normal" or "italic"
        std::string weight;  // "400" or "700"
        std::string url;
    };
    std::vector<Font_variant> variants {};

    std::regex face_rgx(
        R"(@font-face\s*\{[^}]*font-style:\s*(\w+);[^}]*font-weight:\s*(\d+);[^}]*src:\s*url\((https?://[^)]+\.ttf)\)[^}]*\})",
        std::regex::multiline);

    auto begin = std::sregex_iterator(css_response.begin(), css_response.end(), face_rgx);
    auto end = std::sregex_iterator();

    for (auto it = begin; it != end; ++it) {
        Font_variant v {};
        v.style = (*it)[1];
        v.weight = (*it)[2];
        v.url = (*it)[3];
        variants.push_back(v);
    }

    if (variants.empty()) {
        return {};
    }

    // Create cache directory
    std::string cache_base = cache_directory("_fonts");
    if (!fs::exists(cache_base))
        fs::create_directories(cache_base);

    std::string cache_dir = cache_base + "/" + dir_name;
    if (!fs::exists(cache_dir))
        fs::create_directory(cache_dir);

    Resolved_font font {};
    font.family_name = family_name;
    font.dir_name = dir_name;
    font.font_dir = cache_dir;
    font.from_cache = true;

    // Download each .ttf file with descriptive names
    for (auto& v : variants) {
        std::string local_name;
        if (v.style == "normal" && v.weight == "400")
            local_name = "Regular.ttf";
        else if (v.style == "normal" && v.weight == "700")
            local_name = "Bold.ttf";
        else if (v.style == "italic" && v.weight == "400")
            local_name = "Italic.ttf";
        else if (v.style == "italic" && v.weight == "700")
            local_name = "BoldItalic.ttf";
        else
            continue;

        std::string ttf_path = cache_dir + "/" + local_name;

        if (!fs::exists(ttf_path)) {
            std::string dl_cmd = "curl -s -o '" + ttf_path + "' '" + v.url + "'";
            (void)exec(dl_cmd.c_str());
            if (!fs::exists(ttf_path)) {
                (void)K::log(1, "Failed to download font file: " + v.url);
                continue;
            }
        }

        if (local_name == "Regular.ttf")
            font.regular = local_name;
        else if (local_name == "Bold.ttf")
            font.bold = local_name;
        else if (local_name == "Italic.ttf")
            font.italic = local_name;
        else if (local_name == "BoldItalic.ttf")
            font.bold_italic = local_name;
    }

    // Generate @font-face CSS file
    std::string css_file = cache_base + "/" + dir_name + ".css";
    std::stringstream css {};
    auto emit_face = [&](std::string style, std::string weight, std::string filename) {
        if (filename.empty())
            return;
        css << "\n@font-face {\n"
            << "  font-family: '" << family_name << "';\n"
            << "  font-style: " << style << ";\n"
            << "  font-weight: " << weight << ";\n"
            << "  src: url('" << dir_name << "/" << filename << "') format('truetype');\n"
            << "}\n";
    };
    emit_face("normal", "400", font.regular);
    emit_face("normal", "700", font.bold);
    emit_face("italic", "400", font.italic);
    emit_face("italic", "700", font.bold_italic);

    string_to_file(css_file, css.str());
    font.css_file = css_file;

    return font;
}


static void extract_font_metrics(Resolved_font& font)
{
    if (font.regular.empty() || font.font_dir.empty())
        return;
    std::string ttf_path = font.font_dir + "/" + font.regular;
    if (!fs::exists(ttf_path))
        return;
    // Extract both x-height and cap-height ratios from OS/2 table
    std::string script =
        "python3 -c \""
        "import struct; "
        "f = open('" + ttf_path + "', 'rb'); "
        "_, n = struct.unpack('>IH', f.read(6)); "
        "f.read(6); "
        "t = {};\n"
        "for _ in range(n):\n"
        "    tag = f.read(4).decode('latin-1').strip('\\\\x00'); "
        "    _, o, l = struct.unpack('>III', f.read(12)); "
        "    t[tag] = o\n"
        "f.seek(t['head'] + 18); "
        "upm = struct.unpack('>H', f.read(2))[0]; "
        "f.seek(t['OS/2']); "
        "ver = struct.unpack('>H', f.read(2))[0]; "
        "f.seek(t['OS/2'] + 86); "
        "xh, ch = struct.unpack('>hh', f.read(4)); "
        "print(f'{xh/upm:.4f} {ch/upm:.4f}') if ver >= 2 else None; "
        "f.close()\"";
    std::string result = trim(exec(script.c_str()));
    if (!result.empty()) {
        try {
            auto pos = result.find(' ');
            if (pos != std::string::npos) {
                font.xheight_ratio = std::stof(result.substr(0, pos));
                font.capheight_ratio = std::stof(result.substr(pos + 1));
            }
        } catch (...) {}
    }
}

Resolved_font resolve_font(std::string family_name)
{
    if (family_name.empty())
        return {};

    std::string dir_name = name_to_dirname(family_name);

    // 1. Check bundled fonts
    Resolved_font font = resolve_bundled(family_name, dir_name);
    if (!font.family_name.empty()) {
        extract_font_metrics(font);
        return font;
    }

    // 2. Check font cache
    font = resolve_cached(family_name, dir_name);
    if (!font.family_name.empty()) {
        extract_font_metrics(font);
        return font;
    }

    // 3. Fetch from Google Fonts
    font = fetch_google_font(family_name, dir_name);
    if (!font.family_name.empty()) {
        extract_font_metrics(font);
        return font;
    }

    // 4. Error
    throw Argument_error(
        "Font \"" + family_name + "\" not found.\n"
        "  Not bundled in sks/font/fonts/" + dir_name + "/,\n"
        "  not cached, and not available from Google Fonts.\n"
        "  Check the font name or install it locally.");
}


// Font assets are copied with copy_file_stream() (mac/file.h) rather than
// std::filesystem::copy_file, which fails on Apple `container` virtiofs mounts
// — see the note on copy_file_stream() in file.cpp for the full rationale.


void install_resolved_font(const Resolved_font& font, std::string output_dir)
{
    if (font.family_name.empty())
        return;

    std::string output_font_dir = output_dir + "/fonts";
    if (!fs::exists(output_font_dir))
        fs::create_directory(output_font_dir);

    // Copy .css file and font directory to output
    std::string dest_css = output_font_dir + "/" + font.dir_name + ".css";
    std::string dest_dir = output_font_dir + "/" + font.dir_name;

    copy_file_stream(font.css_file, dest_css);

    if (!fs::exists(dest_dir)) {
        fs::create_directory(dest_dir);
        for (auto& entry : fs::directory_iterator(font.font_dir)) {
            copy_file_stream(entry.path(),
                             dest_dir + "/" + entry.path().filename().string());
        }
    }
}
