'use strict';

const http = require('http');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');
const { URL, URLSearchParams } = require('url');

loadEnv(path.join(__dirname, '.env'));

const PORT = Number(process.env.PORT || 3000);
const BASE_URL = process.env.BASE_URL || `http://localhost:${PORT}`;
const PUBLIC_DIR = path.join(__dirname, 'public');
const DATA_DIR = path.join(__dirname, 'data');
const BOOKINGS_FILE = path.join(DATA_DIR, 'bookings.json');
const LEADS_FILE = path.join(DATA_DIR, 'leads.json');
const ADMIN_EMAIL = (process.env.ADMIN_EMAIL || 'admin@inviewra.com').toLowerCase();
const ADMIN_PASSWORD = process.env.ADMIN_PASSWORD || 'change-this-now';
const SESSION_TTL_MS = 12 * 60 * 60 * 1000;
const sessions = new Map();
const rateLimits = new Map();

const PRICING = {
  PL: {
    currency: 'PLN', unit: 'm²', locale: 'pl-PL', taxNote: 'ceny netto',
    tiers: [
      { max: 60, price: 499, labelPL: 'Mieszkanie do 60 m²', labelEN: 'Apartment up to 60 m²' },
      { max: 120, price: 699, labelPL: 'Mieszkanie 61–120 m²', labelEN: 'Apartment 61–120 m²' },
      { max: 200, price: 949, labelPL: 'Dom / lokal 121–200 m²', labelEN: 'House / space 121–200 m²' },
      { max: 350, price: 1390, labelPL: 'Duży dom 201–350 m²', labelEN: 'Large house 201–350 m²' }
    ],
    overMaxPerUnit: 4.5,
    minimumCommercial: 790,
    addons: {
      floorplan: { price: 149, labelPL: 'Plan 2D', labelEN: '2D floor plan' },
      rush: { price: 199, labelPL: 'Realizacja do 24 h', labelEN: '24-hour delivery' },
      google: { price: 199, labelPL: 'Publikacja Google Street View', labelEN: 'Google Street View publishing' },
      tags: { price: 99, labelPL: 'Pakiet 10 interaktywnych tagów', labelEN: '10 interactive tags' },
      hosting: { price: 149, labelPL: 'Dodatkowy rok hostingu', labelEN: 'Additional year of hosting' }
    }
  },
  US: {
    currency: 'USD', unit: 'ft²', locale: 'en-US', taxNote: 'before applicable tax',
    tiers: [
      { max: 1500, price: 249, labelPL: 'Nieruchomość do 1 500 ft²', labelEN: 'Property up to 1,500 ft²' },
      { max: 2500, price: 329, labelPL: 'Nieruchomość 1 501–2 500 ft²', labelEN: 'Property 1,501–2,500 ft²' },
      { max: 4000, price: 449, labelPL: 'Nieruchomość 2 501–4 000 ft²', labelEN: 'Property 2,501–4,000 ft²' },
      { max: 6000, price: 649, labelPL: 'Nieruchomość 4 001–6 000 ft²', labelEN: 'Property 4,001–6,000 ft²' }
    ],
    overMaxPerUnit: 0.11,
    minimumCommercial: 299,
    addons: {
      floorplan: { price: 75, labelPL: 'Plan 2D', labelEN: '2D floor plan' },
      rush: { price: 75, labelPL: 'Realizacja do 24 h', labelEN: '24-hour delivery' },
      google: { price: 99, labelPL: 'Publikacja Google Street View', labelEN: 'Google Street View publishing' },
      tags: { price: 59, labelPL: 'Pakiet 10 interaktywnych tagów', labelEN: '10 interactive tags' },
      hosting: { price: 79, labelPL: 'Dodatkowy rok hostingu', labelEN: 'Additional year of hosting' }
    }
  }
};

function loadEnv(file) {
  if (!fs.existsSync(file)) return;
  for (const line of fs.readFileSync(file, 'utf8').split(/\r?\n/)) {
    const trimmed = line.trim();
    if (!trimmed || trimmed.startsWith('#')) continue;
    const idx = trimmed.indexOf('=');
    if (idx < 1) continue;
    const key = trimmed.slice(0, idx).trim();
    let value = trimmed.slice(idx + 1).trim();
    if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) value = value.slice(1, -1);
    if (!(key in process.env)) process.env[key] = value;
  }
}

function ensureDataFiles() {
  fs.mkdirSync(DATA_DIR, { recursive: true });
  for (const file of [BOOKINGS_FILE, LEADS_FILE]) {
    if (!fs.existsSync(file)) fs.writeFileSync(file, '[]\n');
  }
}
ensureDataFiles();

function readJson(file) {
  try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
  catch { return []; }
}
function writeJson(file, data) {
  const tmp = `${file}.tmp`;
  fs.writeFileSync(tmp, JSON.stringify(data, null, 2));
  fs.renameSync(tmp, file);
}
function json(res, status, body, headers = {}) {
  res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store', ...headers });
  res.end(JSON.stringify(body));
}
function text(res, status, body, contentType = 'text/plain; charset=utf-8', headers = {}) {
  res.writeHead(status, { 'Content-Type': contentType, ...headers });
  res.end(body);
}
function parseCookies(req) {
  return Object.fromEntries((req.headers.cookie || '').split(';').map(v => v.trim()).filter(Boolean).map(v => {
    const i = v.indexOf('='); return [decodeURIComponent(v.slice(0, i)), decodeURIComponent(v.slice(i + 1))];
  }));
}
function getSession(req) {
  const token = parseCookies(req).inviewra_session;
  const session = token && sessions.get(token);
  if (!session || session.expiresAt < Date.now()) {
    if (token) sessions.delete(token);
    return null;
  }
  return session;
}
function requireAdmin(req, res) {
  if (!getSession(req)) {
    json(res, 401, { error: 'Unauthorized' });
    return false;
  }
  return true;
}
function requestIp(req) {
  return String(req.headers['x-forwarded-for'] || req.socket.remoteAddress || 'unknown').split(',')[0].trim();
}
function limited(req, key, max = 12, windowMs = 60_000) {
  const bucketKey = `${requestIp(req)}:${key}`;
  const now = Date.now();
  const bucket = rateLimits.get(bucketKey) || [];
  const active = bucket.filter(ts => now - ts < windowMs);
  active.push(now);
  rateLimits.set(bucketKey, active);
  return active.length > max;
}
async function readBody(req, maxBytes = 150_000) {
  return new Promise((resolve, reject) => {
    let data = '';
    req.on('data', chunk => {
      data += chunk;
      if (Buffer.byteLength(data) > maxBytes) reject(new Error('Payload too large'));
    });
    req.on('end', () => {
      try { resolve(data ? JSON.parse(data) : {}); }
      catch { reject(new Error('Invalid JSON'));
      }
    });
    req.on('error', reject);
  });
}
function clean(value, max = 500) {
  return String(value ?? '').replace(/[<>]/g, '').trim().slice(0, max);
}
function id(prefix) {
  return `${prefix}_${Date.now().toString(36)}_${crypto.randomBytes(5).toString('hex')}`;
}
function money(value, market) {
  const p = PRICING[market];
  return new Intl.NumberFormat(p.locale, { style: 'currency', currency: p.currency, maximumFractionDigits: 0 }).format(value);
}
function calculateQuote(payload) {
  const market = payload.market === 'US' ? 'US' : 'PL';
  const cfg = PRICING[market];
  const area = Math.max(1, Number(payload.area || 0));
  const type = clean(payload.propertyType || 'residential', 40);
  let base = 0;
  const tier = cfg.tiers.find(t => area <= t.max);
  if (type === 'commercial') {
    base = Math.max(cfg.minimumCommercial, Math.round(area * cfg.overMaxPerUnit));
  } else if (tier) {
    base = tier.price;
  } else {
    const highest = cfg.tiers[cfg.tiers.length - 1];
    base = Math.max(highest.price, Math.round(area * cfg.overMaxPerUnit));
  }
  const selected = Array.isArray(payload.addons) ? payload.addons.filter(k => cfg.addons[k]) : [];
  const addOnTotal = selected.reduce((sum, k) => sum + cfg.addons[k].price, 0);
  return { market, area, type, base, addons: selected, total: base + addOnTotal, currency: cfg.currency, unit: cfg.unit };
}
function availability(market) {
  const bookings = readJson(BOOKINGS_FILE).filter(b => b.market === market && !['cancelled'].includes(b.status));
  const reserved = new Set(bookings.map(b => `${b.date}|${b.slot}`));
  const result = [];
  const now = new Date();
  for (let i = 1; i <= 35; i++) {
    const date = new Date(now);
    date.setUTCDate(now.getUTCDate() + i);
    const dow = date.getUTCDay();
    if (dow === 0) continue;
    const iso = date.toISOString().slice(0, 10);
    const slots = dow === 6 ? ['09:00', '12:30'] : ['09:00', '13:00', '16:30'];
    result.push({ date: iso, slots: slots.filter(slot => !reserved.has(`${iso}|${slot}`)) });
  }
  return result;
}
async function sendEmail({ to, subject, html }) {
  const key = process.env.RESEND_API_KEY;
  const from = process.env.FROM_EMAIL;
  if (!key || !from || !to) {
    console.log('[email demo]', { to, subject });
    return { demo: true };
  }
  const response = await fetch('https://api.resend.com/emails', {
    method: 'POST',
    headers: { Authorization: `Bearer ${key}`, 'Content-Type': 'application/json' },
    body: JSON.stringify({ from, to: Array.isArray(to) ? to : [to], subject, html })
  });
  if (!response.ok) throw new Error(`Email provider returned ${response.status}`);
  return response.json();
}
async function notifyBooking(booking) {
  const lang = booking.language === 'en' ? 'en' : 'pl';
  const customerSubject = lang === 'pl' ? `Potwierdzenie zgłoszenia ${booking.reference}` : `Booking request received ${booking.reference}`;
  const customerHtml = lang === 'pl'
    ? `<h2>Dziękujemy za rezerwację INVIEWRA</h2><p>Otrzymaliśmy zgłoszenie na <b>${booking.date}, ${booking.slot}</b>.</p><p>Szacunkowa cena: <b>${money(booking.quote.total, booking.market)}</b>.</p><p>Potwierdzimy termin po weryfikacji adresu i zakresu usługi.</p>`
    : `<h2>Thank you for choosing INVIEWRA</h2><p>We received your request for <b>${booking.date} at ${booking.slot}</b>.</p><p>Estimated price: <b>${money(booking.quote.total, booking.market)}</b>.</p><p>We will confirm the appointment after reviewing the address and scope.</p>`;
  await Promise.allSettled([
    sendEmail({ to: booking.email, subject: customerSubject, html: customerHtml }),
    sendEmail({ to: process.env.NOTIFY_EMAIL || ADMIN_EMAIL, subject: `New INVIEWRA booking — ${booking.reference}`, html: `<h2>New booking</h2><p>${booking.name} — ${booking.email} — ${booking.phone}</p><p>${booking.address}</p><p>${booking.date} ${booking.slot} — ${money(booking.quote.total, booking.market)}</p>` })
  ]);
}
function toCsv(rows) {
  const headers = ['reference','createdAt','status','market','name','email','phone','address','date','slot','area','propertyType','total','currency'];
  const escape = v => `"${String(v ?? '').replaceAll('"', '""')}"`;
  return [headers.join(','), ...rows.map(r => headers.map(h => escape(h === 'total' ? r.quote?.total : h === 'currency' ? r.quote?.currency : h === 'area' ? r.quote?.area : h === 'propertyType' ? r.quote?.type : r[h])).join(','))].join('\n');
}
function bookingIcs(b) {
  const start = `${b.date.replaceAll('-', '')}T${b.slot.replace(':', '')}00`;
  const [h, m] = b.slot.split(':').map(Number);
  const endDate = new Date(`${b.date}T${b.slot}:00Z`);
  endDate.setUTCHours(h + 2, m, 0, 0);
  const end = `${endDate.toISOString().slice(0, 10).replaceAll('-', '')}T${endDate.toISOString().slice(11, 19).replaceAll(':', '')}`;
  const esc = v => String(v || '').replace(/[\\,;]/g, m => `\\${m}`).replace(/\n/g, '\\n');
  return `BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//INVIEWRA//Booking//EN\r\nBEGIN:VEVENT\r\nUID:${b.id}@inviewra.com\r\nDTSTAMP:${new Date().toISOString().replace(/[-:]/g,'').replace(/\.\d{3}/,'')}\r\nDTSTART:${start}\r\nDTEND:${end}\r\nSUMMARY:${esc(`INVIEWRA scan — ${b.name}`)}\r\nLOCATION:${esc(b.address)}\r\nDESCRIPTION:${esc(`${b.reference} | ${b.phone} | ${b.email}`)}\r\nEND:VEVENT\r\nEND:VCALENDAR\r\n`;
}
function contentType(file) {
  const ext = path.extname(file).toLowerCase();
  return ({ '.html':'text/html; charset=utf-8','.css':'text/css; charset=utf-8','.js':'application/javascript; charset=utf-8','.json':'application/json; charset=utf-8','.svg':'image/svg+xml','.png':'image/png','.jpg':'image/jpeg','.jpeg':'image/jpeg','.ico':'image/x-icon','.txt':'text/plain; charset=utf-8','.xml':'application/xml; charset=utf-8' })[ext] || 'application/octet-stream';
}
function serveStatic(req, res, pathname) {
  const requested = pathname === '/' ? '/index.html' : pathname;
  const safe = path.normalize(requested).replace(/^(\.\.[/\\])+/, '');
  const file = path.join(PUBLIC_DIR, safe);
  if (!file.startsWith(PUBLIC_DIR) || !fs.existsSync(file) || fs.statSync(file).isDirectory()) return false;
  const cache = /\.(css|js|svg|png|jpg|jpeg|ico)$/.test(file) ? 'public, max-age=3600' : 'no-cache';
  res.writeHead(200, { 'Content-Type': contentType(file), 'Cache-Control': cache, 'X-Content-Type-Options': 'nosniff' });
  fs.createReadStream(file).pipe(res);
  return true;
}

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, BASE_URL);
  const pathname = decodeURIComponent(url.pathname);
  res.setHeader('X-Frame-Options', 'SAMEORIGIN');
  res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
  res.setHeader('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');

  try {
    if (pathname === '/api/config' && req.method === 'GET') {
      return json(res, 200, { pricing: PRICING, availability: { PL: availability('PL'), US: availability('US') }, brand: { name: 'INVIEWRA', email: 'hello@inviewra.com' } });
    }

    if (pathname === '/api/contact' && req.method === 'POST') {
      if (limited(req, 'contact')) return json(res, 429, { error: 'Too many requests' });
      const body = await readBody(req);
      const lead = {
        id: id('lead'), createdAt: new Date().toISOString(), status: 'new',
        name: clean(body.name, 100), email: clean(body.email, 180).toLowerCase(), phone: clean(body.phone, 60),
        company: clean(body.company, 140), message: clean(body.message, 3000), market: body.market === 'US' ? 'US' : 'PL', language: body.language === 'en' ? 'en' : 'pl'
      };
      if (!lead.name || !/^\S+@\S+\.\S+$/.test(lead.email) || !lead.message) return json(res, 400, { error: 'Missing or invalid fields' });
      const leads = readJson(LEADS_FILE); leads.unshift(lead); writeJson(LEADS_FILE, leads);
      await Promise.allSettled([
        sendEmail({ to: process.env.NOTIFY_EMAIL || ADMIN_EMAIL, subject: `New INVIEWRA lead — ${lead.name}`, html: `<p>${lead.email} ${lead.phone}</p><p>${lead.message}</p>` }),
        sendEmail({ to: lead.email, subject: lead.language === 'pl' ? 'Otrzymaliśmy Twoją wiadomość — INVIEWRA' : 'We received your message — INVIEWRA', html: lead.language === 'pl' ? '<p>Dziękujemy. Odpowiemy możliwie szybko.</p>' : '<p>Thank you. We will respond as soon as possible.</p>' })
      ]);
      return json(res, 201, { ok: true, reference: lead.id });
    }

    if (pathname === '/api/bookings' && req.method === 'POST') {
      if (limited(req, 'booking', 8, 10 * 60_000)) return json(res, 429, { error: 'Too many requests' });
      const body = await readBody(req);
      const quote = calculateQuote(body);
      const booking = {
        id: id('booking'), reference: `INV-${crypto.randomBytes(3).toString('hex').toUpperCase()}`,
        createdAt: new Date().toISOString(), status: 'pending', market: quote.market, language: body.language === 'en' ? 'en' : 'pl',
        name: clean(body.name, 100), email: clean(body.email, 180).toLowerCase(), phone: clean(body.phone, 60),
        company: clean(body.company, 140), address: clean(body.address, 300), city: clean(body.city, 120),
        date: clean(body.date, 10), slot: clean(body.slot, 5), notes: clean(body.notes, 2000), quote
      };
      if (!booking.name || !/^\S+@\S+\.\S+$/.test(booking.email) || !booking.phone || !booking.address || !/^\d{4}-\d{2}-\d{2}$/.test(booking.date) || !/^\d{2}:\d{2}$/.test(booking.slot)) return json(res, 400, { error: 'Missing or invalid fields' });
      const bookings = readJson(BOOKINGS_FILE);
      const conflict = bookings.some(b => b.status !== 'cancelled' && b.market === booking.market && b.date === booking.date && b.slot === booking.slot);
      if (conflict) return json(res, 409, { error: 'This slot was just reserved. Please choose another.' });
      bookings.unshift(booking); writeJson(BOOKINGS_FILE, bookings);
      await notifyBooking(booking);
      return json(res, 201, { ok: true, booking: { id: booking.id, reference: booking.reference, total: quote.total, currency: quote.currency }, checkoutAvailable: Boolean(process.env.STRIPE_SECRET_KEY) });
    }

    if (pathname === '/api/checkout' && req.method === 'POST') {
      const body = await readBody(req);
      const bookings = readJson(BOOKINGS_FILE);
      const booking = bookings.find(b => b.id === clean(body.bookingId, 100));
      if (!booking) return json(res, 404, { error: 'Booking not found' });
      if (!process.env.STRIPE_SECRET_KEY) return json(res, 200, { demo: true, message: 'Stripe is not configured. Add STRIPE_SECRET_KEY in .env.' });
      const cfg = PRICING[booking.market];
      const params = new URLSearchParams({
        mode: 'payment',
        success_url: process.env.STRIPE_SUCCESS_URL || `${BASE_URL}/?payment=success`,
        cancel_url: process.env.STRIPE_CANCEL_URL || `${BASE_URL}/?payment=cancelled`,
        customer_email: booking.email,
        'line_items[0][quantity]': '1',
        'line_items[0][price_data][currency]': cfg.currency.toLowerCase(),
        'line_items[0][price_data][unit_amount]': String(Math.round(booking.quote.total * 100)),
        'line_items[0][price_data][product_data][name]': `INVIEWRA 3D scan — ${booking.reference}`,
        'metadata[booking_id]': booking.id
      });
      const stripeRes = await fetch('https://api.stripe.com/v1/checkout/sessions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Content-Type': 'application/x-www-form-urlencoded' }, body: params });
      const data = await stripeRes.json();
      if (!stripeRes.ok) return json(res, 502, { error: data.error?.message || 'Payment provider error' });
      booking.checkoutSessionId = data.id; booking.status = 'awaiting_payment'; writeJson(BOOKINGS_FILE, bookings);
      return json(res, 200, { url: data.url });
    }

    if (pathname === '/api/admin/login' && req.method === 'POST') {
      if (limited(req, 'login', 6, 15 * 60_000)) return json(res, 429, { error: 'Too many login attempts' });
      const body = await readBody(req);
      const emailOk = crypto.timingSafeEqual(Buffer.from(clean(body.email, 180).toLowerCase().padEnd(200)), Buffer.from(ADMIN_EMAIL.padEnd(200)));
      const password = clean(body.password, 300);
      const passHash = crypto.createHash('sha256').update(password).digest();
      const expected = crypto.createHash('sha256').update(ADMIN_PASSWORD).digest();
      const passOk = crypto.timingSafeEqual(passHash, expected);
      if (!emailOk || !passOk) return json(res, 401, { error: 'Invalid credentials' });
      const token = crypto.randomBytes(32).toString('hex');
      sessions.set(token, { email: ADMIN_EMAIL, expiresAt: Date.now() + SESSION_TTL_MS });
      return json(res, 200, { ok: true }, { 'Set-Cookie': `inviewra_session=${token}; HttpOnly; SameSite=Strict; Path=/; Max-Age=${SESSION_TTL_MS / 1000}${BASE_URL.startsWith('https://') ? '; Secure' : ''}` });
    }

    if (pathname === '/api/admin/logout' && req.method === 'POST') {
      const token = parseCookies(req).inviewra_session;
      if (token) sessions.delete(token);
      return json(res, 200, { ok: true }, { 'Set-Cookie': 'inviewra_session=; HttpOnly; SameSite=Strict; Path=/; Max-Age=0' });
    }

    if (pathname === '/api/admin/me' && req.method === 'GET') {
      const session = getSession(req);
      return json(res, session ? 200 : 401, session ? { email: session.email } : { error: 'Unauthorized' });
    }

    if (pathname === '/api/admin/dashboard' && req.method === 'GET') {
      if (!requireAdmin(req, res)) return;
      const bookings = readJson(BOOKINGS_FILE);
      const leads = readJson(LEADS_FILE);
      const revenue = bookings.filter(b => !['cancelled'].includes(b.status)).reduce((sum, b) => sum + Number(b.quote?.total || 0), 0);
      return json(res, 200, { bookings, leads, stats: { bookings: bookings.length, pending: bookings.filter(b => ['pending','awaiting_payment'].includes(b.status)).length, leads: leads.length, quotedRevenue: revenue } });
    }

    if (pathname === '/api/admin/bookings.csv' && req.method === 'GET') {
      if (!requireAdmin(req, res)) return;
      return text(res, 200, toCsv(readJson(BOOKINGS_FILE)), 'text/csv; charset=utf-8', { 'Content-Disposition': 'attachment; filename="inviewra-bookings.csv"' });
    }

    const statusMatch = pathname.match(/^\/api\/admin\/bookings\/([^/]+)$/);
    if (statusMatch && req.method === 'PATCH') {
      if (!requireAdmin(req, res)) return;
      const body = await readBody(req);
      const allowed = ['pending','confirmed','awaiting_payment','paid','completed','cancelled'];
      if (!allowed.includes(body.status)) return json(res, 400, { error: 'Invalid status' });
      const bookings = readJson(BOOKINGS_FILE);
      const booking = bookings.find(b => b.id === statusMatch[1]);
      if (!booking) return json(res, 404, { error: 'Not found' });
      booking.status = body.status; booking.updatedAt = new Date().toISOString();
      writeJson(BOOKINGS_FILE, bookings);
      return json(res, 200, { ok: true, booking });
    }

    const icsMatch = pathname.match(/^\/api\/bookings\/([^/]+)\.ics$/);
    if (icsMatch && req.method === 'GET') {
      const booking = readJson(BOOKINGS_FILE).find(b => b.id === icsMatch[1]);
      if (!booking) return text(res, 404, 'Not found');
      return text(res, 200, bookingIcs(booking), 'text/calendar; charset=utf-8', { 'Content-Disposition': `attachment; filename="${booking.reference}.ics"` });
    }

    if (serveStatic(req, res, pathname)) return;
    return text(res, 404, 'Not found');
  } catch (error) {
    console.error(error);
    return json(res, 500, { error: 'Unexpected server error' });
  }
});

server.listen(PORT, () => {
  console.log(`INVIEWRA running at ${BASE_URL}`);
  if (ADMIN_PASSWORD === 'change-this-now') console.warn('WARNING: Change ADMIN_PASSWORD before deployment.');
});
