/**
 * GSO Test Runner (no external dependencies)
 * Run: node tests/run.js
 */

// Mock mongoose so MarketSystem can be required without a live DB
const Module = require('module');
const origLoad = Module._load;
Module._load = function(name, ...args) {
  if (name === 'mongoose') {
    const fakeSchema = function(def) { this.def = def; };
    fakeSchema.prototype.index = function() {};
    fakeSchema.Types = { Mixed: 'Mixed' };
    return { Schema: fakeSchema, model: () => class {} };
  }
  return origLoad.call(this, name, ...args);
};

let passed = 0, failed = 0;
function test(name, fn) {
  try { fn(); console.log(`  ✅ ${name}`); passed++; }
  catch(e) { console.log(`  ❌ ${name}\n     ${e.message}`); failed++; }
}
function expect(val) {
  return {
    toBe:           (x) => { if (val !== x) throw new Error(`Expected ${JSON.stringify(x)}, got ${JSON.stringify(val)}`); },
    toBeTruthy:     ()  => { if (!val) throw new Error(`Expected truthy, got ${val}`); },
    toBeGreaterThan:(x) => { if (!(val > x)) throw new Error(`Expected > ${x}, got ${val}`); },
    toBeLessThan:   (x) => { if (!(val < x)) throw new Error(`Expected < ${x}, got ${val}`); },
  };
}

// ─── CraftingSystem ──────────────────────────────────────────────────────────
console.log('\n📦 CraftingSystem');
const CraftingSystem = require('../systems/CraftingSystem');
const mockLoader = {
  getRecipe: (id) => id === 'test_recipe' ? {
    recipe: { inputs: { iron_ore: 2 }, output: { iron_ingot: 1 }, craft_time_seconds: 30 },
    craft: { xp: 10 }
  } : null,
  getAllRecipes: () => [], getRecipesByType: () => [], getCraftingTabs: () => [],
};
const cs = new CraftingSystem(mockLoader);

test('canCraft with sufficient items', () => {
  const r = cs.checkMaterials('test_recipe', { items: [{id:'iron_ore', quantity:3}] });
  if (!r.canCraft) throw new Error('Expected canCraft=true');
});
test('fails with insufficient items', () => {
  const r = cs.checkMaterials('test_recipe', { items: [{id:'iron_ore', quantity:1}] });
  if (r.canCraft) throw new Error('Expected canCraft=false');
  if (r.missing[0].need !== 2) throw new Error(`Expected need=2, got ${r.missing[0].need}`);
});
test('unknown recipe -> canCraft=false', () => {
  const r = cs.checkMaterials('nope', { items: [] });
  if (r.canCraft) throw new Error('Should be false');
});
test('_calcSkillLevel: 0 XP = level 1',  () => expect(cs._calcSkillLevel(0)).toBe(1));
test('_calcSkillLevel: 200 XP = level 2', () => expect(cs._calcSkillLevel(200)).toBe(2));
test('_calcSkillLevel: caps at 50',        () => expect(cs._calcSkillLevel(99999)).toBe(50));
test('_removeItems: reduces quantity', () => {
  const inv = { items: [{id:'iron_ore', quantity:5}] };
  cs._removeItems(inv, 'iron_ore', 3);
  expect(inv.items[0].quantity).toBe(2);
});
test('_removeItems: removes item fully', () => {
  const inv = { items: [{id:'iron_ore', quantity:2}] };
  cs._removeItems(inv, 'iron_ore', 2);
  expect(inv.items.length).toBe(0);
});

// ─── MarketSystem ────────────────────────────────────────────────────────────
console.log('\n💰 MarketSystem');
const { MarketSystem } = require('../systems/MarketSystem');
const ms = new MarketSystem();
test('has 5 tradeable resources',         () => expect(Object.keys(ms.getTradeableResources()).length).toBe(5));
test('darkMatter minPrice = 10',          () => expect(ms.getTradeableResources().darkMatter.minPrice).toBe(10));
test('metal maxPrice = 100',              () => expect(ms.getTradeableResources().metal.maxPrice).toBe(100));

// ─── XP Progression ──────────────────────────────────────────────────────────
console.log('\n⭐ XP Progression (GDD §3.2: 500 × L^1.65)');
const xpReq = (L) => Math.round(500 * Math.pow(L, 1.65));
test('Level 1 = 500 XP',          () => expect(xpReq(1)).toBe(500));
test('Level 5 > Level 2',         () => expect(xpReq(5)).toBeGreaterThan(xpReq(2)));
test('Level 10 > 10,000',         () => expect(xpReq(10)).toBeGreaterThan(10000));
test('Level 50 > 300,000',        () => expect(xpReq(50)).toBeGreaterThan(300000));

// ─── Alliance Research Tree ───────────────────────────────────────────────────
console.log('\n🛡  Alliance Research Tree');
const tree = require('../data/gso/alliance/research_tree.json');
test('3 tiers',                     () => expect(tree.tiers.length).toBe(3));
test('Tier 1 has 3 techs',          () => expect(tree.tiers[0].techs.length).toBe(3));
test('Tier 2 has prerequisites', () => {
  const ok = tree.tiers[1].techs.some(t => t.prereq?.length > 0);
  if (!ok) throw new Error('No tier 2 tech has prerequisites');
});
test('All techs have id/name/cost/effect', () => {
  for (const tier of tree.tiers)
    for (const t of tier.techs)
      if (!t.id||!t.name||!t.cost||!t.effect)
        throw new Error(`Tech missing fields: ${JSON.stringify(t)}`);
});

// ─── Locales ──────────────────────────────────────────────────────────────────
console.log('\n🌍 Locales');
const fs2 = require('fs'), path2 = require('path');
for (const lang of ['en','de','fr','es','ja','zh','ko','pt']) {
  test(`${lang}.json has nav/resources/actions`, () => {
    const locPath = path2.join(__dirname,'../../Client/locales',`${lang}.json`);
    const loc = JSON.parse(fs2.readFileSync(locPath,'utf8'));
    if (!loc.nav?.dashboard) throw new Error('Missing nav.dashboard');
    if (!loc.resources?.metal) throw new Error('Missing resources.metal');
    if (!loc.actions?.build) throw new Error('Missing actions.build');
  });
}

// ─── Recipe time keys ─────────────────────────────────────────────────────────
console.log('\n⏱  Recipe Time Coverage');
const TIME_KEYS = ['craft_time_seconds','alloy_time_seconds','smelt_time_seconds','process_time_seconds','cook_time_seconds','harvest_time_seconds'];

test('alloy recipes have alloy_time_seconds', () => {
  const dir = path2.join(__dirname,'../data/gso/recipes/alloys');
  for (const f of fs2.readdirSync(dir).filter(f=>f.endsWith('.json'))) {
    const rec = JSON.parse(fs2.readFileSync(path2.join(dir,f)));
    if (!TIME_KEYS.some(k=>rec.recipe?.[k]!==undefined)) throw new Error(`${f} missing time key`);
  }
});
test('smelting recipes have smelt_time_seconds', () => {
  const dir = path2.join(__dirname,'../data/gso/recipes/smelting');
  for (const f of fs2.readdirSync(dir).filter(f=>f.endsWith('.json'))) {
    const rec = JSON.parse(fs2.readFileSync(path2.join(dir,f)));
    if (!rec.recipe?.smelt_time_seconds) throw new Error(`${f} missing smelt_time_seconds`);
  }
});

// ─── Summary ──────────────────────────────────────────────────────────────────
console.log(`\n${'─'.repeat(50)}`);
console.log(`Results: ${passed} passed, ${failed} failed`);
if (failed > 0) { console.log('❌ Some tests failed'); process.exit(1); }
else console.log('✅ All tests passed');
