import { createServer } from 'node:http';
import { mkdir, readFile, stat, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { randomUUID } from 'node:crypto';
import { JsonStore } from './store.mjs';
import { createEtsyActions } from './etsy.mjs';
import { loadOrCreateKey } from './security.mjs';
import { channelStatus } from './channels.mjs';

const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');

async function loadEnv(file) {
  try {
    for (const line of (await readFile(file, 'utf8')).split(/\r?\n/)) {
      const match = line.match(/^\s*([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)\s*$/);
      if (!match || process.env[match[1]] !== undefined) continue;
      process.env[match[1]] = match[2].replace(/^(['"])(.*)\1$/, '$2');
    }
  } catch (error) { if (error.code !== 'ENOENT') throw error; }
}
await loadEnv(path.join(root, '.env'));

const dataDir = path.resolve(root, process.env.SHIRE_DATA_DIR || 'data');
const uploadsDir = path.join(dataDir, 'uploads');
await mkdir(uploadsDir, { recursive: true });
const store = new JsonStore(dataDir);
await store.init();
const encryptionKey = await loadOrCreateKey(dataDir);

const config = {
  host: process.env.SHIRE_HOST || '127.0.0.1',
  port: Number(process.env.SHIRE_PORT || 4173),
  etsyApiKey: process.env.ETSY_API_KEY || '',
  etsySharedSecret: process.env.ETSY_SHARED_SECRET || '',
  etsyShopId: process.env.ETSY_SHOP_ID || '',
  etsyShippingProfileId: process.env.ETSY_SHIPPING_PROFILE_ID || '',
  etsyReadinessStateId: process.env.ETSY_READINESS_STATE_ID || '',
  ausPostApiKey: process.env.AUSPOST_API_KEY || '',
  ollamaUrl: (process.env.OLLAMA_URL || 'http://127.0.0.1:11434').replace(/\/$/, ''),
  ollamaModel: process.env.OLLAMA_MODEL || 'qwen2.5:7b',
  allowedRedirectOrigins: (process.env.ALLOWED_REDIRECT_ORIGINS || `http://127.0.0.1:${process.env.SHIRE_PORT || 4173},http://localhost:${process.env.SHIRE_PORT || 4173},http://127.0.0.1:5173,http://localhost:5173`)
    .split(',').map(value => value.trim()).filter(Boolean),
};

const etsy = createEtsyActions({ store, key: encryptionKey, config, dataDir });
const allowedEntities = new Set(['Product', 'Order', 'Record']);

function json(res, status, value) {
  const body = JSON.stringify(value);
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Content-Length': Buffer.byteLength(body), 'Cache-Control': 'no-store' });
  res.end(body);
}

async function bodyJson(req, maxBytes = 15 * 1024 * 1024) {
  let size = 0; const chunks = [];
  for await (const chunk of req) {
    size += chunk.length;
    if (size > maxBytes) throw Object.assign(new Error('Request body is too large'), { status: 413 });
    chunks.push(chunk);
  }
  if (!chunks.length) return {};
  try { return JSON.parse(Buffer.concat(chunks).toString('utf8')); }
  catch { throw Object.assign(new Error('Invalid JSON body'), { status: 400 }); }
}

function cleanRecord(input) {
  if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Record must be an object');
  const output = {};
  for (const [key, value] of Object.entries(input)) {
    if (['__proto__', 'prototype', 'constructor', 'id', 'created_date', 'updated_date'].includes(key)) continue;
    if (value !== undefined) output[key] = value;
  }
  return output;
}

async function entityRoute(req, res, url) {
  const parts = url.pathname.split('/').filter(Boolean);
  const entity = decodeURIComponent(parts[2] || '');
  if (!allowedEntities.has(entity)) return json(res, 404, { error: 'Unknown local entity' });
  const id = parts[3] && parts[3] !== 'bulk' ? decodeURIComponent(parts[3]) : null;

  if (req.method === 'GET' && !id) {
    let records = store.list(entity);
    const rawFilter = url.searchParams.get('filter');
    if (rawFilter) {
      const filters = JSON.parse(rawFilter);
      records = records.filter(item => Object.entries(filters).every(([key, value]) => item[key] === value));
    }
    const sort = url.searchParams.get('sort') || '-created_date';
    const descending = sort.startsWith('-'); const field = descending ? sort.slice(1) : sort;
    records.sort((a, b) => String(a[field] ?? '').localeCompare(String(b[field] ?? '')) * (descending ? -1 : 1));
    return json(res, 200, records.slice(0, Math.min(2000, Number(url.searchParams.get('limit') || 500))));
  }
  if (req.method === 'POST' && !id) return json(res, 201, await store.create(entity, cleanRecord(await bodyJson(req))));
  if (req.method === 'PATCH' && parts[3] === 'bulk') {
    const { updates = [] } = await bodyJson(req);
    const changed = [];
    for (const update of updates.slice(0, 1000)) {
      if (!update.id) continue;
      const result = await store.update(entity, update.id, cleanRecord(update));
      if (result) changed.push(result);
    }
    return json(res, 200, changed);
  }
  if (req.method === 'PATCH' && id) {
    const updated = await store.update(entity, id, cleanRecord(await bodyJson(req)));
    return updated ? json(res, 200, updated) : json(res, 404, { error: 'Record not found' });
  }
  if (req.method === 'DELETE' && id) return json(res, (await store.remove(entity, id)) ? 200 : 404, { success: true });
  return json(res, 405, { error: 'Method not allowed' });
}

function fallbackMarketing(prompt) {
  const name = prompt.match(/called[: ]+"([^"]+)"/i)?.[1] || prompt.match(/for (?:a 3D printed product called )?"([^"]+)"/i)?.[1] || 'Custom 3D Print';
  return {
    tagline: `${name}, brought to life layer by layer`,
    etsy_title: `${name} | Made to Order 3D Printed Collectible | Australian Seller`.slice(0, 140),
    etsy_description: `Bring ${name} into your collection with a made-to-order 3D print produced in Western Australia.\n\n• Printed to order\n• Multiple colours may be available\n• Carefully inspected before dispatch\n• Ideal for display, gifting or painting\n\nMaterial and dimensions: add the confirmed material, measurements and colour before publishing. Minor layer lines are a normal part of the 3D-printing process.\n\nCare: keep away from high heat and prolonged direct sunlight.\n\nPacked carefully and posted from Australia. Please message before ordering if you need a specific deadline.`,
    etsy_tags: '3d printed, made to order, australian made, unique gift, desk decor, collectible, fantasy decor, custom colour, maker gift, display piece, printed gift, home decor, shire 3d',
    square_description: `${name} is made to order by Shire 3D in Western Australia. A distinctive 3D-printed piece for display, gifting or painting. Choose from available colours and contact us for custom requests.`,
    ebay_title: `${name} 3D Printed New Made to Order Australian Seller`.slice(0, 80),
    ebay_description: `Condition: New. ${name} is 3D printed to order in Australia and inspected before dispatch. Includes one finished print unless otherwise stated. Posted carefully from Western Australia.`,
  };
}

async function askOllama(prompt, schema, system) {
  const response = await fetch(`${config.ollamaUrl}/api/chat`, {
    method: 'POST', headers: { 'Content-Type': 'application/json' }, signal: AbortSignal.timeout(120_000),
    body: JSON.stringify({
      model: config.ollamaModel, stream: false, format: schema ? 'json' : undefined,
      messages: [{ role: 'system', content: system || 'You are SHIRE Merchant, a careful Australian small-business assistant.' }, { role: 'user', content: prompt }],
      options: { temperature: 0.4 },
    }),
  });
  if (!response.ok) throw new Error(`Ollama returned ${response.status}`);
  const content = (await response.json()).message?.content || '';
  return schema ? JSON.parse(content) : content.trim();
}

async function aiGenerate(body) {
  try { return await askOllama(body.prompt || '', body.response_json_schema); }
  catch (error) {
    const fallback = fallbackMarketing(body.prompt || '');
    if (body.response_json_schema) return fallback;
    const prompt = body.prompt || '';
    if (/tagline/i.test(prompt)) return fallback.tagline;
    if (/etsy title/i.test(prompt)) return fallback.etsy_title;
    if (/etsy.*tags/i.test(prompt)) return fallback.etsy_tags;
    if (/etsy.*description/i.test(prompt)) return fallback.etsy_description;
    if (/square/i.test(prompt)) return fallback.square_description;
    if (/ebay title/i.test(prompt)) return fallback.ebay_title;
    if (/ebay/i.test(prompt)) return fallback.ebay_description;
    return fallback.tagline;
  }
}

function businessSnapshot() {
  const products = store.list('Product'); const orders = store.list('Order');
  const pending = orders.filter(order => ['Pending', 'Shipped'].includes(order.status));
  return {
    product_count: products.length, order_count: orders.length, pending_orders: pending.length,
    order_revenue: orders.filter(order => order.status !== 'Cancelled').reduce((sum, order) => sum + Number(order.total || 0), 0),
    products: products.slice(0, 100).map(({ id, title, price, etsy_price, category, etsy_url, ebay_url }) => ({ id, title, price, etsy_price, category, etsy_url, ebay_url })),
    pending: pending.slice(0, 50).map(({ id, order_number, marketplace, status, product_title, quantity, total, order_date }) => ({ id, order_number, marketplace, status, product_title, quantity, total, order_date })),
  };
}

async function assistantChat(body) {
  const snapshot = businessSnapshot();
  const system = `You are SHIRE Merchant inside ARMOR OS. Ray Morrison is the sole approval authority. You may analyse local business data and propose work. Never claim to publish, spend money, message customers, delete data, change a marketplace, or perform another external action. Those actions require a proposal and Ray's explicit approval. Be concise, practical, Australian, and focused on helping Shire 3D earn revenue ethically.`;
  const prompt = `Current local business snapshot:\n${JSON.stringify(snapshot)}\n\nRay says: ${String(body.message || '').slice(0, 4000)}\n\nReply with useful next steps. Clearly label any external action as APPROVAL REQUIRED.`;
  try {
    const message = await askOllama(prompt, null, system);
    return { message, mode: 'ollama', model: config.ollamaModel, snapshot_at: new Date().toISOString() };
  } catch {
    const topProducts = snapshot.products.slice(0, 3).map(item => item.title).filter(Boolean).join(', ') || 'No products loaded yet';
    return {
      mode: 'offline', snapshot_at: new Date().toISOString(),
      message: `I’m running in offline assistant mode. You currently have ${snapshot.product_count} products, ${snapshot.pending_orders} pending/shipped orders and $${snapshot.order_revenue.toFixed(2)} in recorded non-cancelled order revenue. Current products: ${topProducts}.\n\nBest next step: complete accurate costs and Etsy-ready copy for your strongest products. Any marketplace publishing will be prepared as a draft and marked APPROVAL REQUIRED. Install Ollama and the configured model for full conversational planning.`,
    };
  }
}

async function ausPostRates(body) {
  if (!config.ausPostApiKey) throw new Error('Add AUSPOST_API_KEY to .env to retrieve live rates');
  for (const field of ['from_postcode', 'to_postcode', 'weight']) if (!body[field]) throw new Error(`${field} is required`);
  const params = new URLSearchParams({
    from_postcode: String(body.from_postcode), to_postcode: String(body.to_postcode), weight: String(body.weight),
    length: String(body.length || 10), width: String(body.width || 10), height: String(body.height || 10),
  });
  const response = await fetch(`https://digitalapi.auspost.com.au/postage/parcel/domestic/service.json?${params}`, {
    headers: { 'AUTH-KEY': config.ausPostApiKey, Accept: 'application/json' },
  });
  if (!response.ok) throw new Error(`Australia Post returned ${response.status}`);
  const raw = await response.json(); const value = raw?.services?.service || [];
  const list = Array.isArray(value) ? value : [value];
  return { services: list.map(item => ({ code: item.code, name: item.name, price: Number.parseFloat(item.price) || 0, type: item.type || (/express/i.test(item.name) ? 'Express Post' : 'Parcel Post'), options: item.options?.option ? (Array.isArray(item.options.option) ? item.options.option : [item.options.option]) : [] })) };
}

async function upload(req, res) {
  const body = await bodyJson(req, 16 * 1024 * 1024);
  if (!body.name || !body.data || !body.type?.startsWith('image/')) throw new Error('A valid image is required');
  const bytes = Buffer.from(body.data, 'base64');
  if (!bytes.length || bytes.length > 10 * 1024 * 1024) throw new Error('Image must be between 1 byte and 10 MB');
  const extensions = { 'image/jpeg': '.jpg', 'image/png': '.png', 'image/webp': '.webp', 'image/gif': '.gif' };
  const extension = extensions[body.type];
  if (!extension) throw new Error('Only JPEG, PNG, WebP and GIF images are supported');
  const filename = `${randomUUID()}${extension}`;
  await writeFile(path.join(uploadsDir, filename), bytes, { mode: 0o600 });
  return json(res, 201, { file_url: `/uploads/${filename}` });
}

const mime = { '.html': 'text/html; charset=utf-8', '.js': 'text/javascript; charset=utf-8', '.css': 'text/css; charset=utf-8', '.json': 'application/json', '.svg': 'image/svg+xml', '.png': 'image/png', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.webp': 'image/webp', '.gif': 'image/gif', '.ico': 'image/x-icon' };
async function staticFile(res, pathname) {
  const dist = path.join(root, 'dist');
  let file = pathname.startsWith('/uploads/') ? path.join(uploadsDir, path.basename(pathname)) : path.join(dist, pathname === '/' ? 'index.html' : pathname);
  if (!file.startsWith(pathname.startsWith('/uploads/') ? uploadsDir : dist)) return false;
  try { if (!(await stat(file)).isFile()) throw new Error('not file'); }
  catch { if (path.extname(pathname)) return false; file = path.join(dist, 'index.html'); }
  const bytes = await readFile(file);
  res.writeHead(200, { 'Content-Type': mime[path.extname(file)] || 'application/octet-stream', 'Content-Length': bytes.length });
  res.end(bytes); return true;
}

const actions = { ...etsy, getAusPostRates: ausPostRates };
const server = createServer(async (req, res) => {
  const url = new URL(req.url, `http://${req.headers.host || `${config.host}:${config.port}`}`);
  try {
    if (url.pathname === '/api/health') return json(res, 200, { ok: true, mode: 'standalone', ai: config.ollamaModel });
    if (url.pathname.startsWith('/api/entities/')) return entityRoute(req, res, url);
    if (url.pathname === '/api/uploads' && req.method === 'POST') return upload(req, res);
    if (url.pathname === '/api/ai/generate' && req.method === 'POST') return json(res, 200, await aiGenerate(await bodyJson(req)));
    if (url.pathname === '/api/assistant/chat' && req.method === 'POST') return json(res, 200, await assistantChat(await bodyJson(req)));
    if (url.pathname === '/api/assistant/status') return json(res, 200, { name: 'SHIRE Merchant', local: true, model: config.ollamaModel, authority: 'Ray approval required for all external actions', channels: channelStatus(config, Boolean(store.getSecret('etsy'))), snapshot: businessSnapshot() });
    if (url.pathname === '/api/channels/status') return json(res, 200, { channels: channelStatus(config, Boolean(store.getSecret('etsy'))) });
    if (url.pathname.startsWith('/api/actions/') && req.method === 'POST') {
      const name = decodeURIComponent(url.pathname.slice('/api/actions/'.length));
      if (!actions[name]) return json(res, 404, { error: 'Unknown action' });
      return json(res, 200, await actions[name](await bodyJson(req)));
    }
    if (await staticFile(res, decodeURIComponent(url.pathname))) return;
    json(res, 404, { error: 'Not found' });
  } catch (error) {
    console.error(`[${new Date().toISOString()}]`, req.method, url.pathname, error.message);
    json(res, error.status || 400, { error: error.message || 'Request failed' });
  }
});

server.listen(config.port, config.host, () => {
  console.log(`SHIRE 3D Manager running at http://${config.host}:${config.port}`);
  console.log(`Data: ${dataDir}`);
  console.log(`AI: ${config.ollamaModel} via ${config.ollamaUrl}`);
});
