const DataService = require('./data');

const buildRouteGraph = (stations, lines, fares, cfg) => {
  const valid = new Set((stations || []).map(s => String(s?.code || '').trim()).filter(Boolean));
  const graph = new Map();
  const segKey = (a, b) => [String(a || '').trim(), String(b || '').trim()].sort().join('|');
  const ensureNode = (code) => {
    if (!graph.has(code)) graph.set(code, new Map());
  };
  const addEdge = (u, v, edge) => {
    if (!u || !v || u === v) return;
    if (!valid.has(u) || !valid.has(v)) return;
    ensureNode(u);
    ensureNode(v);
    const prevUV = graph.get(u).get(v);
    const prevVU = graph.get(v).get(u);
    const next = (() => {
      if (!prevUV) return edge;
      if ((edge?.paid_segments ?? 0) !== (prevUV?.paid_segments ?? 0)) {
        return (edge.paid_segments < prevUV.paid_segments) ? edge : prevUV;
      }
      if ((edge?.transfer_count ?? 0) !== (prevUV?.transfer_count ?? 0)) {
        return (edge.transfer_count < prevUV.transfer_count) ? edge : prevUV;
      }
      const prevFare = (prevUV?.regular ?? 0) + (prevUV?.express ?? 0);
      const nextFare = (edge?.regular ?? 0) + (edge?.express ?? 0);
      return nextFare < prevFare ? edge : prevUV;
    })();
    graph.get(u).set(v, next);
    graph.get(v).set(u, prevVU && prevVU !== prevUV ? prevVU : next);
  };

  for (const code of valid) ensureNode(code);

  const fareBySegment = new Map();
  for (const f of (fares || [])) {
    const a = String(f?.from || '').trim();
    const b = String(f?.to || '').trim();
    if (!a || !b) continue;
    fareBySegment.set(segKey(a, b), {
      regular: Number(f?.cost_regular ?? f?.cost ?? 0) || 0,
      express: Number(f?.cost_express ?? f?.cost ?? (f?.cost_regular ?? f?.cost ?? 0)) || 0
    });
  }

  let segmentEdgeCount = 0;
  for (const line of (lines || [])) {
    const stops = Array.isArray(line?.stations) ? line.stations : (Array.isArray(line?.stops) ? line.stops : []);
    for (let i = 0; i < stops.length - 1; i++) {
      const a = String(stops[i] || '').trim();
      const b = String(stops[i + 1] || '').trim();
      if (!a || !b || a === b) continue;
      const fare = fareBySegment.get(segKey(a, b));
      if (!fare) continue;
      addEdge(a, b, {
        type: 'segment',
        regular: fare.regular,
        express: fare.express,
        paid_segments: 1,
        transfer_count: 0
      });
      segmentEdgeCount += 1;
    }
  }

  if (segmentEdgeCount === 0) {
    for (const [key, fare] of fareBySegment.entries()) {
      const [a, b] = key.split('|');
      addEdge(a, b, {
        type: 'segment',
        regular: fare.regular,
        express: fare.express,
        paid_segments: 1,
        transfer_count: 0
      });
    }
  }

  const addTransferEdge = (u, v) => {
    addEdge(u, v, {
      type: 'transfer',
      regular: 0,
      express: 0,
      paid_segments: 0,
      transfer_count: 1
    });
  };

  const transfers = Array.isArray(cfg?.transfers) ? cfg.transfers : [];
  for (const p of transfers) {
    const u = String(p?.[0] || '').trim();
    const v = String(p?.[1] || '').trim();
    if (!u || !v) continue;
    addTransferEdge(u, v);
  }

  for (const s of (stations || [])) {
    if (!s?.transfer_enabled) continue;
    const from = String(s?.code || '').trim();
    if (!from) continue;
    const tos = Array.isArray(s.transfer_to) ? s.transfer_to : [];
    for (const t of tos) {
      const to = String((typeof t === 'string') ? t : (t?.code || t?.station || t?.id || t?.[0] || '')).trim();
      if (!to) continue;
      addTransferEdge(from, to);
    }
  }

  return graph;
};

const makeRouteCost = (paidSegments = Infinity, transfers = Infinity, hops = Infinity) => ({
  paidSegments,
  transfers,
  hops
});

const compareRouteCost = (a, b) => {
  if ((a?.paidSegments ?? Infinity) !== (b?.paidSegments ?? Infinity)) {
    return (a?.paidSegments ?? Infinity) - (b?.paidSegments ?? Infinity);
  }
  if ((a?.transfers ?? Infinity) !== (b?.transfers ?? Infinity)) {
    return (a?.transfers ?? Infinity) - (b?.transfers ?? Infinity);
  }
  return (a?.hops ?? Infinity) - (b?.hops ?? Infinity);
};

const addRouteCost = (base, edge) => makeRouteCost(
  (base?.paidSegments ?? Infinity) + (edge?.paid_segments ?? 0),
  (base?.transfers ?? Infinity) + (edge?.transfer_count ?? 0),
  (base?.hops ?? Infinity) + 1
);

const findRoutePath = (graph, src, dst) => {
  const dist = new Map();
  const prev = new Map();
  const visited = new Set();
  for (const n of graph.keys()) dist.set(n, makeRouteCost());
  if (!graph.has(src)) return { dist, path: null, prev };
  dist.set(src, makeRouteCost(0, 0, 0));

  while (true) {
    let u = null;
    let best = makeRouteCost();
    for (const n of graph.keys()) {
      if (visited.has(n)) continue;
      const d = dist.get(n);
      if (u == null || compareRouteCost(d, best) < 0) {
        best = d;
        u = n;
      }
    }
    if (u == null || !Number.isFinite(best.paidSegments)) break;
    if (u === dst) break;
    visited.add(u);
    for (const [v, edge] of graph.get(u).entries()) {
      const nd = addRouteCost(best, edge);
      if (compareRouteCost(nd, dist.get(v)) < 0) {
        dist.set(v, nd);
        prev.set(v, u);
      }
    }
  }

  const dstCost = dist.get(dst);
  if (!dstCost || !Number.isFinite(dstCost.paidSegments)) return { dist, path: null, prev };
  const path = [];
  let cur = dst;
  while (cur != null) {
    path.push(cur);
    if (cur === src) break;
    cur = prev.get(cur);
  }
  if (path[path.length - 1] !== src) return { dist, path: null, prev };
  path.reverse();
  return { dist, path, prev };
};

const accumulateFareAlongPath = (graph, path) => {
  if (!Array.isArray(path) || path.length < 2) return { regular: 0, express: 0 };
  let regular = 0;
  let express = 0;
  for (let i = 0; i < path.length - 1; i++) {
    const edge = graph.get(path[i])?.get(path[i + 1]);
    if (!edge) return null;
    if (edge.type === 'transfer') continue;
    regular += Number(edge.regular ?? 0) || 0;
    express += Number(edge.express ?? 0) || 0;
  }
  return { regular, express };
};

const computeTransfersAlongPath = (graph, path) => {
  if (!Array.isArray(path) || path.length < 2) return [];
  const out = new Set();
  for (let i = 0; i < path.length - 1; i++) {
    const a = path[i];
    const b = path[i + 1];
    const edge = graph.get(a)?.get(b);
    if (edge?.type === 'transfer') {
      out.add(a);
      out.add(b);
    }
  }
  return Array.from(out);
};

const LogicService = {
  // Compute ticket price based on fares and lines
  computePrice: (fromCode, toCode, trainType) => {
    try {
      const cfg = DataService.getConfig();
      const baseBoth = LogicService.computeFareBoth(fromCode, toCode);
      const baseReg = Number(baseBoth?.regular ?? 0) || 0;
      const baseExp = Number(baseBoth?.express ?? 0) || 0;
      const discount = Number(cfg?.promotion?.discount ?? 1);
      const base = (trainType === 'Express') ? baseExp : baseReg;
      return Math.floor(base * (discount > 0 ? discount : 1));
    } catch (_) { return 0; }
  },

  computeFareBoth: (fromCode, toCode) => {
    try {
      const src = String(fromCode || '').trim();
      const dst = String(toCode || '').trim();
      if (!src || !dst) return null;
      if (src === dst) {
        return {
          regular: 0,
          express: 0,
          regular_path: [src],
          express_path: [src],
          regular_transfers: [],
          express_transfers: []
        };
      }

      const cfg = DataService.getConfig();
      const stations = DataService.getStations();
      const lines = DataService.getLines();
      const fares = DataService.getFares();
      const graph = buildRouteGraph(stations, lines, fares, cfg);
      const result = findRoutePath(graph, src, dst);
      if (!Array.isArray(result.path)) return null;
      const totals = accumulateFareAlongPath(graph, result.path);
      if (!totals) return null;
      const transfers = computeTransfersAlongPath(graph, result.path);

      return {
        regular: Math.round(Number(totals.regular ?? 0) || 0),
        express: Math.round(Number(totals.express ?? 0) || 0),
        regular_path: result.path,
        express_path: result.path,
        regular_transfers: transfers,
        express_transfers: transfers,
      };
    } catch (_) { return null; }
  },

  accumulateLineFare: (fromCode, toCode, fares, lines) => {
    if (!Array.isArray(lines)) return null;
    const line = lines.find(l => {
        const stations = Array.isArray(l.stations) ? l.stations : (Array.isArray(l.stops) ? l.stops : []);
        return stations.includes(fromCode) && stations.includes(toCode);
    });
    if (!line) return null;
    const arr = Array.isArray(line.stations) ? line.stations : (Array.isArray(line.stops) ? line.stops : []);
    const i = arr.indexOf(fromCode);
    const j = arr.indexOf(toCode);
    if (i === -1 || j === -1) return null;
    const start = Math.min(i, j);
    const end = Math.max(i, j);
    let regular = 0, express = 0;
    for (let k = start; k < end; k++) {
      const a = arr[k], b = arr[k + 1];
      const f = fares.find(x => (x.from === a && x.to === b) || (x.from === b && x.to === a));
      if (!f) return null;
      const vr = Number((f.cost_regular ?? f.cost ?? 0)) || 0;
      const ve = Number((f.cost_express ?? f.cost ?? 0)) || 0;
      regular += vr;
      express += ve;
    }
    return { regular, express };
  },

  genVoucherCode: () => {
    const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ123456789';
    const pick = (n) => Array.from({ length: n }, () => chars[Math.floor(Math.random() * chars.length)]).join('');
    let code = pick(5);
    const idx = DataService.getOrderIndex();
    while (idx[code]) { code = pick(5); }
    return code;
  },

  // Helper for SVG generation
  buildStationNameMap: () => {
    try {
      const arr = DataService.getStations();
      const map = {};
      for (const s of arr) {
        map[s.code] = s.name || s.cn_name || s.station_name || s.code;
      }
      return map;
    } catch (_) { return {}; }
  },

  generateLightFareMapSVG: () => {
    const stations = DataService.getStations();
    const lines = DataService.getLines();
    const fares = DataService.getFares();
    const nameByCode = LogicService.buildStationNameMap(); 
    return require('./svg-generator').generate(stations, lines, fares, nameByCode);
  }
};

module.exports = LogicService;
