using System;
using System.Collections.Generic;
using System.Text.Json;
using Npgsql;
using PhoenixLib.Logging;
using WingsAPI.Data.Drops;
using WingsAPI.Data.Shops;
using WingsEmu.DTOs.Maps;
using WingsEmu.DTOs.Recipes;
using WingsEmu.DTOs.ServerDatas;
using WingsEmu.DTOs.Shops;
using WingsEmu.Game.Configurations.Miniland;

namespace WingsEmu.Plugins.BasicImplementations.ServerConfigs.Persistence;

public static class ParserDataPostgresSync
{
    private static bool Enabled => !string.Equals(Environment.GetEnvironmentVariable("PARSER_DB_SYNC"), "false", StringComparison.OrdinalIgnoreCase);

    private static string BuildConnectionString()
    {
        string host = Environment.GetEnvironmentVariable("DATABASE_IP")
                      ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_IP")
                      ?? "127.0.0.1";
        string port = Environment.GetEnvironmentVariable("DATABASE_PORT")
                      ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PORT")
                      ?? "5432";
        string db = Environment.GetEnvironmentVariable("DATABASE_NAME")
                    ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_NAME")
                    ?? "game";
        string user = Environment.GetEnvironmentVariable("DATABASE_USER")
                      ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_USER")
                      ?? "postgres";
        string pass = Environment.GetEnvironmentVariable("DATABASE_PASSWORD")
                      ?? Environment.GetEnvironmentVariable("POSTGRES_DATABASE_PASSWORD")
                      ?? "postgres";

        return $"Host={host};Port={port};Database={db};Username={user};Password={pass}";
    }

    public static void SyncMapsAndPortals(IReadOnlyList<ServerMapDto> maps, IReadOnlyList<PortalDTO> portals)
    {
        if (!Enabled)
        {
            return;
        }

        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();

            using (var cmd = new NpgsqlCommand(@"
CREATE TABLE IF NOT EXISTS server_maps (
  map_id INT PRIMARY KEY,
  map_vnum INT,
  map_name_id INT,
  map_music_id INT
);
CREATE TABLE IF NOT EXISTS server_map_flags (
  map_id INT NOT NULL,
  flag TEXT NOT NULL,
  PRIMARY KEY(map_id, flag)
);
CREATE TABLE IF NOT EXISTS map_portals (
  id BIGSERIAL PRIMARY KEY,
  destination_map_id INT,
  destination_map_x INT,
  destination_map_y INT,
  source_map_id INT,
  source_map_x INT,
  source_map_y INT,
  type INT
);", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE server_map_flags, server_maps, map_portals RESTART IDENTITY;", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            foreach (ServerMapDto map in maps)
            {
                using var cmd = new NpgsqlCommand("INSERT INTO server_maps(map_id,map_vnum,map_name_id,map_music_id) VALUES (@id,@vnum,@name,@music);", conn, tx);
                cmd.Parameters.AddWithValue("id", map.Id);
                cmd.Parameters.AddWithValue("vnum", map.MapVnum);
                cmd.Parameters.AddWithValue("name", map.NameId);
                cmd.Parameters.AddWithValue("music", map.MusicId);
                cmd.ExecuteNonQuery();

                if (map.Flags == null)
                {
                    continue;
                }

                foreach (MapFlags flag in map.Flags)
                {
                    using var fcmd = new NpgsqlCommand("INSERT INTO server_map_flags(map_id,flag) VALUES (@id,@flag);", conn, tx);
                    fcmd.Parameters.AddWithValue("id", map.Id);
                    fcmd.Parameters.AddWithValue("flag", flag.ToString());
                    fcmd.ExecuteNonQuery();
                }
            }

            foreach (PortalDTO portal in portals)
            {
                using var cmd = new NpgsqlCommand(@"INSERT INTO map_portals(destination_map_id,destination_map_x,destination_map_y,source_map_id,source_map_x,source_map_y,type)
VALUES (@dmid,@dmx,@dmy,@smid,@smx,@smy,@type);", conn, tx);
                cmd.Parameters.AddWithValue("dmid", portal.DestinationMapId);
                cmd.Parameters.AddWithValue("dmx", portal.DestinationX);
                cmd.Parameters.AddWithValue("dmy", portal.DestinationY);
                cmd.Parameters.AddWithValue("smid", portal.SourceMapId);
                cmd.Parameters.AddWithValue("smx", portal.SourceX);
                cmd.Parameters.AddWithValue("smy", portal.SourceY);
                cmd.Parameters.AddWithValue("type", portal.Type);
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced maps={maps.Count} portals={portals.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync maps/portals", ex);
        }
    }

    public static void SyncMapNpcs(IReadOnlyList<MapNpcDTO> npcs)
    {
        if (!Enabled)
        {
            return;
        }

        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();

            using (var cmd = new NpgsqlCommand(@"
CREATE TABLE IF NOT EXISTS map_npcs (
  id BIGSERIAL PRIMARY KEY,
  map_npc_id INT,
  map_id INT,
  vnum INT,
  pos_x INT,
  pos_y INT,
  effect_vnum INT,
  effect_delay INT,
  dialog_id INT,
  direction_facing INT
);", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_npcs RESTART IDENTITY;", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            foreach (MapNpcDTO npc in npcs)
            {
                using var cmd = new NpgsqlCommand(@"INSERT INTO map_npcs(map_npc_id,map_id,vnum,pos_x,pos_y,effect_vnum,effect_delay,dialog_id,direction_facing)
VALUES (@id,@map,@vnum,@x,@y,@ev,@ed,@dialog,@dir);", conn, tx);
                cmd.Parameters.AddWithValue("id", npc.Id);
                cmd.Parameters.AddWithValue("map", npc.MapId);
                cmd.Parameters.AddWithValue("vnum", npc.NpcVNum);
                cmd.Parameters.AddWithValue("x", npc.MapX);
                cmd.Parameters.AddWithValue("y", npc.MapY);
                cmd.Parameters.AddWithValue("ev", npc.Effect);
                cmd.Parameters.AddWithValue("ed", npc.EffectDelay);
                cmd.Parameters.AddWithValue("dialog", npc.Dialog);
                cmd.Parameters.AddWithValue("dir", npc.Direction);
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced map_npcs={npcs.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync map_npcs", ex);
        }
    }

    public static void SyncMapMonsters(IReadOnlyList<MapMonsterDTO> monsters)
    {
        if (!Enabled)
        {
            return;
        }

        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();

            using (var cmd = new NpgsqlCommand(@"
CREATE TABLE IF NOT EXISTS map_monsters (
  id BIGSERIAL PRIMARY KEY,
  map_monster_id INT,
  map_id INT,
  vnum INT,
  map_x INT,
  map_y INT,
  direction INT,
  can_move BOOLEAN
);
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS direction INT;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS can_move BOOLEAN;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_monster_id INT;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_id INT;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS vnum INT;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_x INT;
ALTER TABLE map_monsters ADD COLUMN IF NOT EXISTS map_y INT;", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_monsters RESTART IDENTITY;", conn, tx))
            {
                cmd.ExecuteNonQuery();
            }

            foreach (MapMonsterDTO monster in monsters)
            {
                using var cmd = new NpgsqlCommand(@"INSERT INTO map_monsters(map_monster_id,map_id,vnum,map_x,map_y,direction,can_move)
VALUES (@id,@map,@vnum,@x,@y,@dir,@move);", conn, tx);
                cmd.Parameters.AddWithValue("id", monster.Id);
                cmd.Parameters.AddWithValue("map", monster.MapId);
                cmd.Parameters.AddWithValue("vnum", monster.MonsterVNum);
                cmd.Parameters.AddWithValue("x", monster.MapX);
                cmd.Parameters.AddWithValue("y", monster.MapY);
                cmd.Parameters.AddWithValue("dir", monster.Direction);
                cmd.Parameters.AddWithValue("move", monster.IsMoving);
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced map_monsters={monsters.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync map_monsters", ex);
        }
    }

    public static void SyncTeleporters(IReadOnlyList<TeleporterDTO> teleporters)
    {
        if (!Enabled) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();
            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS map_teleporters (
  id BIGSERIAL PRIMARY KEY,
  teleporter_id INT,
  idx INT,
  type INT,
  map_id INT,
  map_npc_id INT,
  map_x INT,
  map_y INT
);", conn, tx)) cmd.ExecuteNonQuery();
            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE map_teleporters RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            foreach (var t in teleporters)
            {
                using var cmd = new NpgsqlCommand(@"INSERT INTO map_teleporters(teleporter_id,idx,type,map_id,map_npc_id,map_x,map_y)
VALUES (@id,@idx,@type,@map,@npc,@x,@y);", conn, tx);
                cmd.Parameters.AddWithValue("id", t.Id);
                cmd.Parameters.AddWithValue("idx", t.Index);
                cmd.Parameters.AddWithValue("type", (int)t.Type);
                cmd.Parameters.AddWithValue("map", t.MapId);
                cmd.Parameters.AddWithValue("npc", t.MapNpcId);
                cmd.Parameters.AddWithValue("x", t.MapX);
                cmd.Parameters.AddWithValue("y", t.MapY);
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced map_teleporters={teleporters.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync map_teleporters", ex);
        }
    }

    public static void SyncShops(IReadOnlyList<ShopDTO> shops)
    {
        if (!Enabled) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();
            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS shops (
  id BIGSERIAL PRIMARY KEY,
  map_npc_id INT,
  menu_type INT,
  shop_type INT,
  name TEXT
);
CREATE TABLE IF NOT EXISTS shop_items (
  id BIGSERIAL PRIMARY KEY,
  map_npc_id INT,
  slot INT,
  color INT,
  item_vnum INT,
  rare INT,
  type INT,
  upgrade INT,
  price INT
);
CREATE TABLE IF NOT EXISTS shop_skills (
  id BIGSERIAL PRIMARY KEY,
  map_npc_id INT,
  skill_vnum INT,
  slot INT,
  type INT
);", conn, tx)) cmd.ExecuteNonQuery();
            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE shops, shop_items, shop_skills RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            foreach (var s in shops)
            {
                using (var cmd = new NpgsqlCommand("INSERT INTO shops(map_npc_id,menu_type,shop_type,name) VALUES (@npc,@menu,@type,@name);", conn, tx))
                {
                    cmd.Parameters.AddWithValue("npc", s.MapNpcId);
                    cmd.Parameters.AddWithValue("menu", s.MenuType);
                    cmd.Parameters.AddWithValue("type", s.ShopType);
                    cmd.Parameters.AddWithValue("name", (object?)s.Name ?? DBNull.Value);
                    cmd.ExecuteNonQuery();
                }

                if (s.Items != null)
                {
                    foreach (ShopItemDTO it in s.Items)
                    {
                        using var cmd = new NpgsqlCommand(@"INSERT INTO shop_items(map_npc_id,slot,color,item_vnum,rare,type,upgrade,price)
VALUES (@npc,@slot,@color,@vnum,@rare,@type,@upg,@price);", conn, tx);
                        cmd.Parameters.AddWithValue("npc", s.MapNpcId);
                        cmd.Parameters.AddWithValue("slot", it.Slot);
                        cmd.Parameters.AddWithValue("color", it.Color);
                        cmd.Parameters.AddWithValue("vnum", it.ItemVNum);
                        cmd.Parameters.AddWithValue("rare", it.Rare);
                        cmd.Parameters.AddWithValue("type", it.Type);
                        cmd.Parameters.AddWithValue("upg", it.Upgrade);
                        cmd.Parameters.AddWithValue("price", (object?)it.Price ?? DBNull.Value);
                        cmd.ExecuteNonQuery();
                    }
                }

                if (s.Skills != null)
                {
                    foreach (ShopSkillDTO sk in s.Skills)
                    {
                        using var cmd = new NpgsqlCommand("INSERT INTO shop_skills(map_npc_id,skill_vnum,slot,type) VALUES (@npc,@skill,@slot,@type);", conn, tx);
                        cmd.Parameters.AddWithValue("npc", s.MapNpcId);
                        cmd.Parameters.AddWithValue("skill", sk.SkillVNum);
                        cmd.Parameters.AddWithValue("slot", sk.Slot);
                        cmd.Parameters.AddWithValue("type", sk.Type);
                        cmd.ExecuteNonQuery();
                    }
                }
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced shops={shops.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync shops", ex);
        }
    }

    public static void SyncItemBoxes(IReadOnlyList<ItemBoxDto> itemBoxes)
    {
        if (!Enabled) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();
            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS item_boxes (
  id BIGSERIAL PRIMARY KEY,
  item_vnum INT,
  box_type INT,
  min_rewards INT,
  max_rewards INT,
  shows_raid_panel BOOLEAN
);
CREATE TABLE IF NOT EXISTS item_box_items (
  id BIGSERIAL PRIMARY KEY,
  item_vnum INT,
  probability INT,
  min_original_rare INT,
  max_original_rare INT,
  generated_amount INT,
  generated_vnum INT,
  generated_random_rarity BOOLEAN,
  generated_upgrade INT
);", conn, tx)) cmd.ExecuteNonQuery();
            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE item_boxes, item_box_items RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            foreach (var b in itemBoxes)
            {
                using (var cmd = new NpgsqlCommand(@"INSERT INTO item_boxes(item_vnum,box_type,min_rewards,max_rewards,shows_raid_panel)
VALUES (@vnum,@type,@min,@max,@raid);", conn, tx))
                {
                    cmd.Parameters.AddWithValue("vnum", b.Id);
                    cmd.Parameters.AddWithValue("type", (int)b.ItemBoxType);
                    cmd.Parameters.AddWithValue("min", (object?)b.MinimumRewards ?? DBNull.Value);
                    cmd.Parameters.AddWithValue("max", (object?)b.MaximumRewards ?? DBNull.Value);
                    cmd.Parameters.AddWithValue("raid", b.ShowsRaidBoxPanelOnOpen);
                    cmd.ExecuteNonQuery();
                }

                if (b.Items == null) continue;
                foreach (var it in b.Items)
                {
                    using var cmd = new NpgsqlCommand(@"INSERT INTO item_box_items(item_vnum,probability,min_original_rare,max_original_rare,generated_amount,generated_vnum,generated_random_rarity,generated_upgrade)
VALUES (@vnum,@prob,@minr,@maxr,@amt,@gv,@rr,@upg);", conn, tx);
                    cmd.Parameters.AddWithValue("vnum", b.Id);
                    cmd.Parameters.AddWithValue("prob", it.Probability);
                    cmd.Parameters.AddWithValue("minr", it.MinimumOriginalItemRare);
                    cmd.Parameters.AddWithValue("maxr", it.MaximumOriginalItemRare);
                    cmd.Parameters.AddWithValue("amt", it.ItemGeneratedAmount);
                    cmd.Parameters.AddWithValue("gv", it.ItemGeneratedVNum);
                    cmd.Parameters.AddWithValue("rr", it.ItemGeneratedRandomRarity);
                    cmd.Parameters.AddWithValue("upg", it.ItemGeneratedUpgrade);
                    cmd.ExecuteNonQuery();
                }
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced item_boxes={itemBoxes.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync item_boxes", ex);
        }
    }

    public static void SyncDrops(IReadOnlyList<DropDTO> drops)
    {
        if (!Enabled) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();
            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS drops (
  id BIGSERIAL PRIMARY KEY,
  drop_id INT,
  amount INT,
  drop_chance INT,
  item_vnum INT,
  map_id INT,
  monster_vnum INT,
  race_type INT,
  race_sub_type INT
);", conn, tx)) cmd.ExecuteNonQuery();
            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE drops RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            foreach (var d in drops)
            {
                using var cmd = new NpgsqlCommand(@"INSERT INTO drops(drop_id,amount,drop_chance,item_vnum,map_id,monster_vnum,race_type,race_sub_type)
VALUES (@id,@amount,@chance,@item,@map,@mon,@race,@subrace);", conn, tx);
                cmd.Parameters.AddWithValue("id", d.Id);
                cmd.Parameters.AddWithValue("amount", d.Amount);
                cmd.Parameters.AddWithValue("chance", d.DropChance);
                cmd.Parameters.AddWithValue("item", d.ItemVNum);
                cmd.Parameters.AddWithValue("map", (object?)d.MapId ?? DBNull.Value);
                cmd.Parameters.AddWithValue("mon", (object?)d.MonsterVNum ?? DBNull.Value);
                cmd.Parameters.AddWithValue("race", (object?)d.RaceType ?? DBNull.Value);
                cmd.Parameters.AddWithValue("subrace", (object?)d.RaceSubType ?? DBNull.Value);
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced drops={drops.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync drops", ex);
        }
    }

    public static void SyncMinigames(MinigameConfiguration minigameConfiguration)
    {
        if (!Enabled || minigameConfiguration == null) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();

            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS minigame_config (
  id BIGSERIAL PRIMARY KEY,
  minigame_vnum INT,
  minigame_type INT,
  minimum_level INT,
  minimum_reputation INT,
  rewards_json JSONB
);
CREATE TABLE IF NOT EXISTS minigame_scores_holders (
  id BIGSERIAL PRIMARY KEY,
  minigame_type INT,
  scores_json JSONB
);
CREATE TABLE IF NOT EXISTS global_minigame_config (
  id BIGSERIAL PRIMARY KEY,
  config_json JSONB
);", conn, tx)) cmd.ExecuteNonQuery();

            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE minigame_config, minigame_scores_holders, global_minigame_config RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            if (minigameConfiguration.Minigames != null)
            {
                foreach (Minigame m in minigameConfiguration.Minigames)
                {
                    using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_config(minigame_vnum,minigame_type,minimum_level,minimum_reputation,rewards_json)
VALUES (@vnum,@type,@minLvl,@minRep,@rewards::jsonb);", conn, tx);
                    cmd.Parameters.AddWithValue("vnum", m.Vnum);
                    cmd.Parameters.AddWithValue("type", (int)m.Type);
                    cmd.Parameters.AddWithValue("minLvl", m.MinimumLevel);
                    cmd.Parameters.AddWithValue("minRep", m.MinimumReputation);
                    cmd.Parameters.AddWithValue("rewards", JsonSerializer.Serialize(m.Rewards));
                    cmd.ExecuteNonQuery();
                }
            }

            if (minigameConfiguration.ScoresHolders != null)
            {
                foreach (MinigameScoresHolder s in minigameConfiguration.ScoresHolders)
                {
                    using var cmd = new NpgsqlCommand(@"INSERT INTO minigame_scores_holders(minigame_type,scores_json)
VALUES (@type,@scores::jsonb);", conn, tx);
                    cmd.Parameters.AddWithValue("type", (int)s.Type);
                    cmd.Parameters.AddWithValue("scores", JsonSerializer.Serialize(s.Scores));
                    cmd.ExecuteNonQuery();
                }
            }

            using (var cmd = new NpgsqlCommand("INSERT INTO global_minigame_config(config_json) VALUES (@cfg::jsonb);", conn, tx))
            {
                cmd.Parameters.AddWithValue("cfg", JsonSerializer.Serialize(minigameConfiguration.Configuration));
                cmd.ExecuteNonQuery();
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced minigames={minigameConfiguration.Minigames?.Count ?? 0} holders={minigameConfiguration.ScoresHolders?.Count ?? 0}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync minigames", ex);
        }
    }

    public static void SyncRecipes(IReadOnlyList<RecipeDTO> recipes)
    {
        if (!Enabled) return;
        try
        {
            using var conn = new NpgsqlConnection(BuildConnectionString());
            conn.Open();
            using var tx = conn.BeginTransaction();
            using (var cmd = new NpgsqlCommand(@"CREATE TABLE IF NOT EXISTS recipes (
  id BIGSERIAL PRIMARY KEY,
  recipe_id INT,
  amount INT,
  producer_map_npc_id INT,
  produced_item_vnum INT,
  producer_item_vnum INT,
  producer_npc_vnum INT
);
CREATE TABLE IF NOT EXISTS recipe_items (
  id BIGSERIAL PRIMARY KEY,
  recipe_id INT,
  slot INT,
  item_vnum INT,
  amount INT
);", conn, tx)) cmd.ExecuteNonQuery();
            using (var cmd = new NpgsqlCommand("TRUNCATE TABLE recipes, recipe_items RESTART IDENTITY;", conn, tx)) cmd.ExecuteNonQuery();

            int fallbackRecipeId = 1;
            foreach (var r in recipes)
            {
                int effectiveRecipeId = r.Id > 0 ? r.Id : fallbackRecipeId;
                fallbackRecipeId = Math.Max(fallbackRecipeId + 1, effectiveRecipeId + 1);

                using (var cmd = new NpgsqlCommand(@"INSERT INTO recipes(recipe_id,amount,producer_map_npc_id,produced_item_vnum,producer_item_vnum,producer_npc_vnum)
VALUES (@id,@amount,@mapNpc,@produced,@prodItem,@prodNpc);", conn, tx))
                {
                    cmd.Parameters.AddWithValue("id", effectiveRecipeId);
                    cmd.Parameters.AddWithValue("amount", r.Amount);
                    cmd.Parameters.AddWithValue("mapNpc", (object?)r.ProducerMapNpcId ?? DBNull.Value);
                    cmd.Parameters.AddWithValue("produced", r.ProducedItemVnum);
                    cmd.Parameters.AddWithValue("prodItem", (object?)r.ProducerItemVnum ?? DBNull.Value);
                    cmd.Parameters.AddWithValue("prodNpc", (object?)r.ProducerNpcVnum ?? DBNull.Value);
                    cmd.ExecuteNonQuery();
                }

                if (r.Items == null) continue;
                foreach (RecipeItemDTO it in r.Items)
                {
                    using var cmd = new NpgsqlCommand("INSERT INTO recipe_items(recipe_id,slot,item_vnum,amount) VALUES (@id,@slot,@item,@amount);", conn, tx);
                    cmd.Parameters.AddWithValue("id", effectiveRecipeId);
                    cmd.Parameters.AddWithValue("slot", it.Slot);
                    cmd.Parameters.AddWithValue("item", it.ItemVNum);
                    cmd.Parameters.AddWithValue("amount", it.Amount);
                    cmd.ExecuteNonQuery();
                }
            }

            tx.Commit();
            Log.Info($"[PARSER_DB_SYNC] Synced recipes={recipes.Count}");
        }
        catch (Exception ex)
        {
            Log.Error("[PARSER_DB_SYNC] Failed to sync recipes", ex);
        }
    }
}
