#include "AssetManager.h"
#include "utils.h"
#include <fstream>
#include <iostream>
#include "../audio/AudioEngine.h"
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>



std::unordered_map<uint64_t, std::shared_ptr<AssetInfo>> AssetManager::s_Assets;
std::unordered_map<std::string, uint64_t> AssetManager::s_PathToUAID;
uint64_t AssetManager::s_NextUAID = 1;

const char *MiniaudioResultToString(ma_result result)
{
    switch (result)
    {
    case MA_SUCCESS:
        return "Success";
    case MA_ERROR:
        return "Generic error";
    case MA_INVALID_ARGS:
        return "Invalid arguments";
    case MA_INVALID_OPERATION:
        return "Invalid operation";
    case MA_OUT_OF_MEMORY:
        return "Out of memory";
    case MA_OUT_OF_RANGE:
        return "Out of range";
    case MA_ACCESS_DENIED:
        return "Access denied";
    case MA_DOES_NOT_EXIST:
        return "File does not exist";
    case MA_ALREADY_EXISTS:
        return "File already exists";
    case MA_TOO_MANY_OPEN_FILES:
        return "Too many open files";
    case MA_INVALID_FILE:
        return "Invalid or corrupted file";
    case MA_TOO_BIG:
        return "File too big";
    case MA_PATH_TOO_LONG:
        return "Path too long";
    case MA_NAME_TOO_LONG:
        return "Name too long";
    case MA_NOT_DIRECTORY:
        return "Not a directory";
    case MA_IS_DIRECTORY:
        return "Is a directory";
    case MA_DIRECTORY_NOT_EMPTY:
        return "Directory not empty";
    case MA_AT_END:
        return "End of file or stream";
    case MA_NO_SPACE:
        return "No space left on device";
    case MA_BUSY:
        return "Resource busy";
    case MA_IO_ERROR:
        return "I/O error";
    case MA_INTERRUPT:
        return "Operation interrupted";
    case MA_UNAVAILABLE:
        return "Resource unavailable";
    case MA_ALREADY_IN_USE:
        return "Already in use";
    case MA_BAD_ADDRESS:
        return "Bad memory address";
    case MA_BAD_SEEK:
        return "Bad seek operation";
    case MA_BAD_PIPE:
        return "Bad pipe";
    case MA_DEADLOCK:
        return "Deadlock detected";
    case MA_TOO_MANY_LINKS:
        return "Too many symbolic links";
    case MA_NOT_IMPLEMENTED:
        return "Not implemented";
    case MA_NO_MESSAGE:
        return "No message available";
    case MA_BAD_MESSAGE:
        return "Bad message format";
    case MA_NO_DATA_AVAILABLE:
        return "No data available";
    case MA_INVALID_DATA:
        return "Invalid data";
    case MA_TIMEOUT:
        return "Operation timed out";
    case MA_NO_NETWORK:
        return "Network unavailable";
    case MA_NOT_UNIQUE:
        return "Not unique";
    case MA_NOT_SOCKET:
        return "Not a socket";
    case MA_NO_ADDRESS:
        return "No address available";
    case MA_BAD_PROTOCOL:
        return "Bad protocol";
    case MA_PROTOCOL_UNAVAILABLE:
        return "Protocol unavailable";
    case MA_PROTOCOL_NOT_SUPPORTED:
        return "Protocol not supported";
    case MA_PROTOCOL_FAMILY_NOT_SUPPORTED:
        return "Protocol family not supported";
    case MA_ADDRESS_FAMILY_NOT_SUPPORTED:
        return "Address family not supported";
    case MA_SOCKET_NOT_SUPPORTED:
        return "Socket not supported";
    case MA_CONNECTION_RESET:
        return "Connection reset";
    case MA_ALREADY_CONNECTED:
        return "Already connected";
    case MA_NOT_CONNECTED:
        return "Not connected";
    case MA_CONNECTION_REFUSED:
        return "Connection refused";
    case MA_NO_HOST:
        return "Host not found";
    case MA_IN_PROGRESS:
        return "Operation in progress";
    case MA_CANCELLED:
        return "Operation cancelled";
    case MA_MEMORY_ALREADY_MAPPED:
        return "Memory already mapped";

    case MA_CRC_MISMATCH:
        return "CRC mismatch";

    case MA_FORMAT_NOT_SUPPORTED:
        return "Audio format not supported";
    case MA_DEVICE_TYPE_NOT_SUPPORTED:
        return "Device type not supported";
    case MA_SHARE_MODE_NOT_SUPPORTED:
        return "Share mode not supported";
    case MA_NO_BACKEND:
        return "No backend available";
    case MA_NO_DEVICE:
        return "No device available";
    case MA_API_NOT_FOUND:
        return "API not found";
    case MA_INVALID_DEVICE_CONFIG:
        return "Invalid device configuration";
    case MA_LOOP:
        return "Loop detected";
    case MA_BACKEND_NOT_ENABLED:
        return "Backend not enabled";

    case MA_DEVICE_NOT_INITIALIZED:
        return "Device not initialized";
    case MA_DEVICE_ALREADY_INITIALIZED:
        return "Device already initialized";
    case MA_DEVICE_NOT_STARTED:
        return "Device not started";
    case MA_DEVICE_NOT_STOPPED:
        return "Device not stopped";
    case MA_FAILED_TO_INIT_BACKEND:
        return "Failed to initialize backend";
    case MA_FAILED_TO_OPEN_BACKEND_DEVICE:
        return "Failed to open backend device";
    case MA_FAILED_TO_START_BACKEND_DEVICE:
        return "Failed to start backend device";
    case MA_FAILED_TO_STOP_BACKEND_DEVICE:
        return "Failed to stop backend device";

    default:
        return "Unknown error code";
    }
}



void AssetManager::Init()
{
    Logger::LogOk("AssetManager Core");

}

uint64_t AssetManager::GenerateUAID()
{
    return s_NextUAID++;
}

void AssetManager::LoadAssetAsync(const std::string &path, AssetType type)
{
    if (auto itPath = s_PathToUAID.find(path); itPath != s_PathToUAID.end() && s_Assets[itPath->second]->loaded)
    {
        Logger::LogVerbose("[AssetManager] Skipping already-loaded asset: %s (UAID: %llu)", path.c_str(), itPath->second);
        return;
    }

    uint64_t uaid = GenerateUAID();
    std::shared_ptr<AssetInfo> asset;

    if (type == AssetType::Image)
        asset = std::make_shared<ImageAssetInfo>();
    else if (type == AssetType::Audio)
        asset = std::make_shared<AudioAssetInfo>();
    else
    {
        Logger::LogWarning("[AssetManager] Unknown Asset Type: '%d'", static_cast<int>(type));
        return;
    }

    asset->uaid = uaid;
    asset->path = path;
    asset->filename = GetFilenameFromPath(path);
    asset->filetype = ::GetFileExtension(path);
    asset->type = type;
    s_Assets[uaid] = asset;
    s_PathToUAID[path] = uaid;

    if (type == AssetType::Image)
        LoadImageInternal(path, uaid);
    else if (type == AssetType::Audio)
        LoadAudioInternal(path, uaid);
}

void AssetManager::LoadImageInternal(const std::string &path, uint64_t uaid)
{
    auto it = s_Assets.find(uaid);
    if (it == s_Assets.end())
        return;

    auto image = std::dynamic_pointer_cast<ImageAssetInfo>(it->second);
    if (!image)
        return;

    stbi_set_flip_vertically_on_load(false);

    std::ifstream file(path, std::ios::binary);
    if (!file)
    {
        Logger::LogError("[AssetManager] Failed to open image file: %s", path.c_str());
        return;
    }

    std::vector<unsigned char> fileData((std::istreambuf_iterator<char>(file)), {});
    int w, h, channels;
    stbi_uc *pixels = stbi_load_from_memory(fileData.data(), static_cast<int>(fileData.size()), &w, &h, &channels, STBI_rgb_alpha);

    if (!pixels)
    {
        Logger::LogError("[AssetManager] Failed to decode image: %s", path.c_str());
        return;
    }

    GLuint texID;
    glGenTextures(1, &texID);
    glBindTexture(GL_TEXTURE_2D, texID);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
    glBindTexture(GL_TEXTURE_2D, 0);
    stbi_image_free(pixels);

    if (texID == 0)
    {
        Logger::LogError("[AssetManager] Failed to create GL texture.");
        return;
    }

    image->size = {static_cast<float>(w), static_cast<float>(h)};
    image->textureID = texID;
    image->channels = channels;
    image->format = "GL_RGBA";
    image->loaded = true;
    image->lastModified = GetFileLastWrite(path);
    image->hash = GetFileHash(path);

    Logger::LogVerbose("[AssetManager] Loaded image: %s (%dx%d)", path.c_str(), w, h);
}

void AssetManager::LoadAudioInternal(const std::string &path, uint64_t uaid)
{
    auto it = s_Assets.find(uaid);
    if (it == s_Assets.end())
        return;

    auto audio = std::dynamic_pointer_cast<AudioAssetInfo>(it->second);
    if (!audio)
        return;

    audio->sound = new ma_sound();
    ma_result result = ma_sound_init_from_file(AudioEngine::GetEngine(), path.c_str(), 0, nullptr, nullptr, audio->sound);

    if (result != MA_SUCCESS)
    {
        Logger::LogError("[AssetManager] Failed to load audio file: %s (Reason: %s)", path.c_str(), MiniaudioResultToString(result));

        delete audio->sound;
        audio->sound = nullptr;
        return;
    }

    audio->loaded = true;
    audio->lastModified = GetFileLastWrite(path);
    audio->hash = GetFileHash(path);

    Logger::LogVerbose("[AssetManager] Loaded audio: %s", path.c_str());
}

const AssetInfo *AssetManager::GetAssetByID(uint64_t uaid)
{
    auto it = s_Assets.find(uaid);
    return it != s_Assets.end() ? it->second.get() : nullptr;
}

const AssetInfo *AssetManager::GetAssetByPath(const std::string &path)
{
    auto it = s_PathToUAID.find(path);
    return it != s_PathToUAID.end() ? s_Assets[it->second].get() : nullptr;
}

const std::unordered_map<uint64_t, std::shared_ptr<AssetInfo>> &AssetManager::GetAllAssets()
{
    return s_Assets;
}

void AssetManager::UnloadAsset(uint64_t uaid)
{
    auto it = s_Assets.find(uaid);
    if (it != s_Assets.end())
    {
        if (auto img = std::dynamic_pointer_cast<ImageAssetInfo>(it->second); img && img->textureID)
            glDeleteTextures(1, &img->textureID);
        if (auto audio = std::dynamic_pointer_cast<AudioAssetInfo>(it->second); audio && audio->sound)
        {
            ma_sound_uninit(audio->sound);
            delete audio->sound;
        }
        s_PathToUAID.erase(it->second->path);
        s_Assets.erase(it);
    }
}

void AssetManager::ClearAllAssets()
{
    for (auto &[id, asset] : s_Assets)
        UnloadAsset(id);

    s_Assets.clear();
    s_PathToUAID.clear();
}

void AssetManager::Save(YAML::Emitter &out)
{
    out << YAML::Key << "Assets" << YAML::BeginSeq;
    for (const auto &[uaid, asset] : s_Assets)
    {
        out << YAML::BeginMap;
        out << YAML::Key << "uaid" << YAML::Value << asset->uaid;
        out << YAML::Key << "path" << YAML::Value << asset->path;
        out << YAML::Key << "filename" << YAML::Value << asset->filename;
        out << YAML::Key << "filetype" << YAML::Value << asset->filetype;
        out << YAML::Key << "type" << YAML::Value << static_cast<int>(asset->type);
        out << YAML::Key << "hash" << YAML::Value << asset->hash;
        out << YAML::Key << "lastModified" << YAML::Value << asset->lastModified;
        asset->Save(out);
        out << YAML::EndMap;
    }
    out << YAML::EndSeq;
}

void AssetManager::Load(const YAML::Node &node)
{
    if (!node)
        return;

    for (const auto &item : node)
    {
        uint64_t uaid = item["uaid"].as<uint64_t>();
        std::string path = item["path"].as<std::string>();
        AssetType type = static_cast<AssetType>(item["type"].as<int>());

        std::shared_ptr<AssetInfo> asset;
        if (type == AssetType::Image)
            asset = std::make_shared<ImageAssetInfo>();
        else if (type == AssetType::Audio)
            asset = std::make_shared<AudioAssetInfo>();
        else
            continue;

        asset->uaid = uaid;
        asset->path = path;
        asset->filename = item["filename"] ? item["filename"].as<std::string>() : GetFilenameFromPath(path);
        asset->filetype = item["filetype"] ? item["filetype"].as<std::string>() : GetFileExtension(path);
        asset->hash = item["hash"] ? item["hash"].as<std::string>() : "";
        asset->lastModified = item["lastModified"] ? item["lastModified"].as<std::time_t>() : 0;
        asset->loaded = false;

        asset->Load(item);

        s_Assets[uaid] = asset;
        s_PathToUAID[path] = uaid;

        if (uaid >= s_NextUAID)
            s_NextUAID = uaid + 1;

        if (type == AssetType::Image)
            LoadImageInternal(path, uaid);
        else if (type == AssetType::Audio)
            LoadAudioInternal(path, uaid);
    }
}

void ImageAssetInfo::Save(YAML::Emitter &out) const
{
    out << YAML::Key << "size" << YAML::Value << YAML::Flow << YAML::BeginSeq << size.x << size.y << YAML::EndSeq;
    out << YAML::Key << "channels" << YAML::Value << channels;
    out << YAML::Key << "format" << YAML::Value << format;
}

void ImageAssetInfo::Load(const YAML::Node &node)
{
    if (node["size"])
    {
        size.x = node["size"][0].as<float>();
        size.y = node["size"][1].as<float>();
    }
    channels = node["channels"] ? node["channels"].as<int>() : 4;
    format = node["format"] ? node["format"].as<std::string>() : "GL_RGBA";
}

void AudioAssetInfo::Save(YAML::Emitter &out) const {}
void AudioAssetInfo::Load(const YAML::Node &node) {}
