const express = require('express');
const crypto = require('crypto');
const router = express.Router();
const DataService = require('../services/data');
const LogicService = require('../services/logic');
const io = require('../io');
const svgGenerator = require('../services/svg-generator');

// Helper to get IP
const getIp = (req) => (req.headers['x-forwarded-for'] || '').toString().split(',')[0].trim() || req.ip || req.connection?.remoteAddress || '';

const appendReqLog = (req, { category, type, detail, source, level } = {}) => {
    const entry = {
        ts: new Date().toISOString(),
        ip: getIp(req),
        ua: String(req.headers['user-agent'] || ''),
        method: req.method,
        path: req.originalUrl || req.path || '',
        category: String(category || '').trim() || 'public',
        source: source == null ? undefined : String(source || '').trim(),
        level: level == null ? undefined : String(level || '').trim(),
        type: String(type || '').trim() || 'event',
        detail: (detail === undefined) ? null : detail
    };
    DataService.appendLog(entry);
};

const buildStationResolver = () => {
    const stations = DataService.getStations?.() || [];
    const codeByKey = new Map();
    const nameToCode = new Map();
    const normKey = (v) => String(v || '').trim().toUpperCase().replace(/\s+/g, '');
    const looksLikeStationCode = (v) => /^[A-Z0-9]+(?:-[A-Z0-9]+)+$/i.test(String(v || '').trim());
    for (const s of stations) {
        if (!s) continue;
        const code = String(s.code || '').trim();
        if (!code) continue;
        codeByKey.set(normKey(code), code);
        const cn = String(s.name || s.cn_name || s.station_name || '').trim();
        if (cn) nameToCode.set(normKey(cn), code);
        const en = String(s.en_name || s.en || s.enName || '').trim();
        if (en) nameToCode.set(normKey(en), code);
    }
    return (v) => {
        const raw = String(v || '').trim();
        if (!raw) return '';
        const key = normKey(raw);
        if (codeByKey.has(key)) return codeByKey.get(key);
        if (looksLikeStationCode(raw)) return raw;
        return nameToCode.get(key) || raw;
    };
};

const normalizeTicketId = (v) => {
    const s0 = String(v || '').replace(/\s+/g, '');
    if (!s0) return '';
    const m = s0.match(/^([A-Za-z]{2})-?([0-9]+)$/);
    if (m) {
        const prefix = m[1].toUpperCase();
        let num = m[2];
        if (num.length < 8) num = num.padStart(8, '0');
        else if (num.length > 8) num = num.slice(-8);
        return `${prefix}-${num}`;
    }
    return s0;
};

const normalizeIcCardId = (v) => {
    const s0 = String(v || '').replace(/\s+/g, '').toUpperCase();
    if (!s0) return '';
    const m = s0.match(/^IC-?([0-9]+)$/);
    if (m) return `IC-${m[1].padStart(6, '0').slice(-6)}`;
    return s0;
};

const buildIcCardId = () => {
    const idx = DataService.getIcCardIndex() || {};
    let id = '';
    do {
        id = `IC-${String(crypto.randomInt(0, 1000000)).padStart(6, '0')}`;
    } while (idx[id]);
    return id;
};

const buildIcCardOrderCode = () => {
    const cards = DataService.getIcCards() || [];
    let code = LogicService.genVoucherCode();
    while (cards.some((item) => String(item?.order_code || item?.voucher_code || item?.code || '').trim().toUpperCase() === code)) {
        code = LogicService.genVoucherCode();
    }
    return code;
};

const getIcCardCatalog = () => ([
    {
        id: 'stored_value',
        name: '储值卡',
        description: '支持反复充值，适合日常乘车。',
        deposit: 0,
        min_initial_balance: 1,
        recommended_initial_balance: 5,
        fixed_amount: null,
        recharge_options: [5, 10, 15, 20]
    }
]);

const getIcCardPlan = (cardType) => {
    const plans = getIcCardCatalog();
    return plans.find((item) => item.id === String(cardType || '').trim()) || plans[0];
};

const IC_CARD_HOLDER_NAME_RE = /^[A-Za-z][A-Za-z .,'()&@/_\-+]*$/;

const displayIcCardId = (card) => {
    const status = String(card?.status || '').trim().toLowerCase();
    const source = String(card?.source || '').trim().toLowerCase();
    const rawId = String(card?.card_id || '').trim();
    if (status === 'pending_pickup' && source === 'online') return '待出卡';
    return rawId || '---';
};

const presentIcCard = (card) => card ? ({
    ...card,
    display_card_id: displayIcCardId(card)
}) : card;

const mapIcCardStatus = (status) => {
    const s = String(status || '').trim().toLowerCase();
    if (s === 'pending_pickup') return '待领卡';
    if (s === 'active') return '正常';
    if (s === 'disabled') return '停用';
    if (s === 'lost') return '挂失';
    if (s === 'refunded') return '已退卡';
    return status || '未知';
};

const mapIcCardType = (type) => {
    const t = String(type || '').trim().toLowerCase();
    if (t === 'stored_value') return '储值卡';
    if (t === 'monthly') return '月票卡';
    if (t === 'tourist') return '纪念卡';
    return type || '未分类';
};

// Basic Info
router.get('/health', (req, res) => res.json({ ok: true }));

router.get('/stations', (req, res) => {
    const list = DataService.getStations();
    res.json(list.map(s => ({
        name: s.name || s.cn_name || s.station_name || '',
        en_name: s.en_name || s.enName || '',
        code: s.code || ''
    })));
});

router.get('/lines', (req, res) => {
    const list = DataService.getLines();
    const map = LogicService.buildStationNameMap();
    const nameFor = (code) => (map && map[code]) || code;
    res.json(list.map(l => ({
        line_id: l.id || '',
        name: l.name || l.cn_name || l.en_name || '',
        color: l.color || l.colour || '',
        stop_names: Array.isArray(l.stations) ? l.stations.map(c => nameFor(c)) : (Array.isArray(l.stops) ? l.stops.map(c => nameFor(c)) : []),
        stops: Array.isArray(l.stations) ? l.stations : (Array.isArray(l.stops) ? l.stops : [])
    })));
});

router.get('/fares', (req, res) => {
    const list = DataService.getFares();
    const map = LogicService.buildStationNameMap();
    const nameFor = (code) => (map && map[code]) || code;
    res.json(list.map(f => ({
        from_name: nameFor(f.from),
        to_name: nameFor(f.to),
        regular_fare: f.cost_regular ?? f.cost ?? 0,
        express_fare: f.cost_express ?? f.cost ?? 0
    })));
});

router.get('/fares/query', (req, res) => {
    const { from, to } = req.query;
    if (!from || !to) {
        appendReqLog(req, { category: 'public', type: 'fare_query_invalid', level: 'warn', detail: { from, to } });
        return res.status(400).json({ error: 'missing_from_or_to' });
    }

    const resolveStation = buildStationResolver();
    const fromCode = resolveStation(from);
    const toCode = resolveStation(to);
    const result = LogicService.computeFareBoth(fromCode, toCode);
    if (result) {
        const cfg = DataService.getConfig();
        const discountRaw = Number(cfg?.promotion?.discount ?? 1);
        const discount = Number.isFinite(discountRaw) && discountRaw > 0 ? discountRaw : 1;
        const regularBase = Number(result.regular);
        const expressBase = Number(result.express);
        const regularDiscounted = Number.isFinite(regularBase) ? Math.floor(regularBase * discount) : null;
        const expressDiscounted = Number.isFinite(expressBase) ? Math.floor(expressBase * discount) : null;
        appendReqLog(req, { category: 'public', type: 'fare_query', detail: { from, to, from_code: fromCode, to_code: toCode, ok: true } });
        res.json({
            from_code: fromCode || null,
            to_code: toCode || null,
            regular_fare: result.regular ?? null,
            express_fare: result.express ?? null,
            discounted_regular_fare: regularDiscounted,
            discounted_express_fare: expressDiscounted,
            discount,
            regular_path: result.regular_path ?? null,
            express_path: result.express_path ?? null,
            regular_transfers: result.regular_transfers ?? null,
            express_transfers: result.express_transfers ?? null
        });
    } else {
        appendReqLog(req, { category: 'public', type: 'fare_query', detail: { from, to, from_code: fromCode, to_code: toCode, ok: false } });
        res.json({ error: 'fare_not_found' });
    }
});

router.get('/config', (req, res) => {
    const cfg = DataService.getConfig();
    res.json({
        promotion: { name: cfg.promotion?.name || '', discount: cfg.promotion?.discount ?? 1 }
    });
});

router.get('/ic-cards/config', (req, res) => {
    const plan = getIcCardPlan('stored_value');
    res.json({
        ok: true,
        cards: getIcCardCatalog(),
        recharge_options: Array.isArray(plan.recharge_options) ? plan.recharge_options : [5, 10, 15, 20],
        initial_balance_min: Number(plan.min_initial_balance || 1) || 1,
        holder_name_pattern: IC_CARD_HOLDER_NAME_RE.source,
        holder_name_hint: '仅支持英文与常用符号'
    });
});

// Ticket Orders
router.post('/orders', async (req, res) => {
    const { start, terminal, train_type, trips, ride_date } = req.body || {};
    const from = String(start || '').trim();
    const to = String(terminal || '').trim();
    const type = String(train_type || '').trim() || 'Local';
    const t = Math.max(1, Number(trips || 1));
    const date = String(ride_date || '').trim();
    if (!from || !to || !date) {
        appendReqLog(req, { category: 'public', type: 'order_create_invalid', level: 'warn', detail: { start: from, terminal: to, ride_date: date } });
        return res.status(400).json({ ok: false, error: 'missing start/terminal/ride_date' });
    }
    const resolveStation = buildStationResolver();
    const fromCode = resolveStation(from);
    const toCode = resolveStation(to);
    const priceSingle = LogicService.computePrice(fromCode, toCode, type);
    const price = Math.max(0, priceSingle * t);
    const code = LogicService.genVoucherCode();
    const rec = { code, start: fromCode, terminal: toCode, train_type: type, trips: t, ride_date: date, price, created_ts: Date.now(), consumed: false };
    
    const list = DataService.getOrders();
    list.push(rec);
    await DataService.saveOrders(list);
    
    const idx = DataService.getOrderIndex();
    idx[code] = rec;
    await DataService.saveOrderIndex(idx);
    
    io.emit('order:created', rec);
    appendReqLog(req, { category: 'public', type: 'order_create', detail: { code, start: from, terminal: to, start_code: fromCode, terminal_code: toCode, train_type: type, trips: t, ride_date: date, price } });
    
    res.json({ ok: true, code, price });
});

// Voucher Detail
router.get('/orders/:code', (req, res) => {
    const code = String(req.params.code || '').trim().toUpperCase();
    if (!code) return res.status(400).json({ ok: false, error: 'code required' });
    const idx = DataService.getOrderIndex();
    const order = idx[code];
    if (!order) {
        appendReqLog(req, { category: 'public', type: 'order_query', detail: { code, ok: false } });
        return res.status(404).json({ ok: false, error: 'not found' });
    }
    const map = LogicService.buildStationNameMap();
    const nameFor = (c) => (map && map[c]) || c;
    const stations = DataService.getStations() || [];
    const enNameFor = (c) => {
        const s = stations.find(x => x.code === c);
        return s ? (s.en_name || s.enName || '') : '';
    };

    appendReqLog(req, { category: 'public', type: 'order_query', detail: { code, ok: true } });
    res.json({
        ok: true,
        data: {
            ...order,
            start_name: nameFor(order.start),
            start_en: enNameFor(order.start),
            terminal_name: nameFor(order.terminal),
            terminal_en: enNameFor(order.terminal)
        }
    });
});

router.post('/orders/:code/consume', async (req, res) => {
    const code = String(req.params.code || '').trim().toUpperCase();
    if (!code) return res.status(400).json({ ok: false, error: 'code required' });
    
    const idx = DataService.getOrderIndex();
    const order = idx[code];
    
    if (!order) {
        appendReqLog(req, { category: 'device', type: 'order_consume', detail: { code, ok: false, reason: 'not_found', device: req.body?.device } });
        return res.status(404).json({ ok: false, error: 'not found' });
    }
    if (order.consumed) {
        appendReqLog(req, { category: 'device', type: 'order_consume', detail: { code, ok: false, reason: 'already_consumed', device: req.body?.device } });
        return res.status(409).json({ ok: false, error: 'already consumed' });
    }
    
    // Mark as consumed
    order.consumed = true;
    order.consumed_ts = Date.now();
    order.device = req.body.device || 'ticket_machine'; // optional tracking
    
    // Update main list
    const list = DataService.getOrders();
    const listIdx = list.findIndex(o => o.code === code);
    if (listIdx >= 0) {
        list[listIdx] = order;
        await DataService.saveOrders(list);
    }
    
    // Update index
    idx[code] = order;
    await DataService.saveOrderIndex(idx);
    
    io.emit('order:consumed', order);
    appendReqLog(req, { category: 'device', type: 'order_consume', detail: { code, ok: true, device: order.device } });
    
    res.json({ ok: true, code });
});

// IC Card Public APIs
router.get('/ic-cards/query', async (req, res) => {
    const q = String(req.query.q || '').trim();
    if (!q) {
        appendReqLog(req, { category: 'public', type: 'ic_card_query_invalid', level: 'warn', detail: { q } });
        return res.status(400).json({ ok: false, error: 'query required' });
    }
    const normCardId = normalizeIcCardId(q);
    const normOrderCode = String(q || '').trim().toUpperCase();
    const card = (DataService.getIcCards() || []).find((item) => {
        const cardId = normalizeIcCardId(item?.card_id);
        const orderCode = String(item?.order_code || '').trim().toUpperCase();
        const voucherCode = String(item?.voucher_code || item?.code || '').trim().toUpperCase();
        return cardId === normCardId || orderCode === normOrderCode || voucherCode === normOrderCode;
    });
    if (!card) {
        appendReqLog(req, { category: 'public', type: 'ic_card_query', detail: { q, ok: false } });
        return res.status(404).json({ ok: false, error: 'ic card not found' });
    }
    const events = await DataService.getIcCardEvents(card.card_id);
    appendReqLog(req, { category: 'public', type: 'ic_card_query', detail: { q, card_id: card.card_id, ok: true } });
    res.json({
        ok: true,
        card: {
            ...presentIcCard(card),
            status_label: mapIcCardStatus(card.status),
            card_type_label: mapIcCardType(card.card_type)
        },
        events
    });
});

router.get('/ic-cards/orders/:code', async (req, res) => {
    const code = String(req.params.code || '').trim().toUpperCase();
    if (!code) return res.status(400).json({ ok: false, error: 'code required' });
    const card = (DataService.getIcCards() || []).find((item) => {
        const orderCode = String(item?.order_code || '').trim().toUpperCase();
        const voucherCode = String(item?.voucher_code || item?.code || '').trim().toUpperCase();
        return orderCode === code || voucherCode === code;
    });
    if (!card) {
        appendReqLog(req, { category: 'public', type: 'ic_card_order_query', detail: { code, ok: false } });
        return res.status(404).json({ ok: false, error: 'not found' });
    }
    appendReqLog(req, { category: 'public', type: 'ic_card_order_query', detail: { code, ok: true, card_id: card.card_id } });
    res.json({
        ok: true,
        data: {
            ...presentIcCard(card),
            status_label: mapIcCardStatus(card.status),
            card_type_label: mapIcCardType(card.card_type)
        }
    });
});

router.post('/ic-cards/orders', async (req, res) => {
    try {
        const body = req.body || {};
        const holder_name = String(body.holder_name || '').trim();
        if (!holder_name) {
            appendReqLog(req, { category: 'public', type: 'ic_card_order_invalid', level: 'warn', detail: { error: 'holder_name required', payload: body } });
            return res.status(400).json({ ok: false, error: 'holder_name required' });
        }
        if (!IC_CARD_HOLDER_NAME_RE.test(holder_name)) {
            appendReqLog(req, { category: 'public', type: 'ic_card_order_invalid', level: 'warn', detail: { error: 'holder_name pattern invalid', payload: body } });
            return res.status(400).json({ ok: false, error: 'holder_name must use English letters and symbols only' });
        }
        const plan = getIcCardPlan('stored_value');
        const initial_balance = Math.floor(Math.max(0, Number(body.initial_balance ?? plan.recommended_initial_balance ?? 0) || 0));
        if (initial_balance < Number(plan.min_initial_balance || 1)) {
            appendReqLog(req, { category: 'public', type: 'ic_card_order_invalid', level: 'warn', detail: { error: 'initial balance too low', payload: body } });
            return res.status(400).json({ ok: false, error: `initial_balance must be >= ${plan.min_initial_balance}` });
        }
        const now = Date.now();
        const card_id = buildIcCardId();
        const order_code = buildIcCardOrderCode();
        const purchase_amount = initial_balance;
        const card = {
            card_id,
            order_code,
            voucher_code: order_code,
            code: order_code,
            holder_name,
            card_type: plan.id,
            status: 'pending_pickup',
            balance: initial_balance,
            deposit: 0,
            purchase_amount,
            source: 'online',
            created_ts: now,
            last_update_ts: now
        };
        await DataService.upsertIcCard(card);
        await DataService.appendIcCardEvent({
            ts: now,
            type: 'order_created',
            card_id,
            order_code,
            detail: {
                holder_name,
                card_type: plan.id,
                purchase_amount,
                balance: initial_balance
            }
        });
        io.emit('ic-card:created', { card_id, order_code, status: 'pending_pickup' });
        appendReqLog(req, { category: 'public', type: 'ic_card_order_create', detail: { card_id, order_code, card_type: plan.id, purchase_amount } });
        res.json({
            ok: true,
            code: order_code,
            card_id,
            display_card_id: displayIcCardId(card),
            amount: purchase_amount,
            card: {
                ...presentIcCard(card),
                status_label: mapIcCardStatus(card.status),
                card_type_label: mapIcCardType(card.card_type)
            }
        });
    } catch (e) {
        appendReqLog(req, { category: 'system', type: 'ic_card_order_create_failed', level: 'error', detail: { error: e?.message || String(e), payload: req.body } });
        res.status(500).json({ ok: false, error: 'failed to create ic card order' });
    }
});

router.post('/tickets/record', async (req, res) => {
    const b = req.body || {};
    const ticket_id = normalizeTicketId(b.ticket_id || b.id);
    if (!ticket_id) {
        appendReqLog(req, { category: 'device', type: 'ticket_record_invalid', level: 'warn', detail: { error: 'ticket_id required', payload: b } });
        return res.status(400).json({ ok: false, error: 'ticket_id required' });
    }

    const start = String(b.start || b.start_station_id || b.start_station || '').trim();
    const terminal = String(b.terminal || b.terminal_station_id || b.end_station || '').trim();
    const train_type = String(b.train_type || b.trainType || b.type || '').trim();
    const cost = Number(b.cost || 0) || 0;
    const station_code = String(b.station_code || b.stationCode || '').trim();
    const device = String(b.device || b.device_id || b.deviceId || 'unknown');

    const trips_total = (b.trips_total == null) ? undefined : (Number(b.trips_total) || 0);
    const trips_remaining = (b.trips_remaining == null) ? undefined : (Number(b.trips_remaining) || 0);

    const ev = {
        ts: Date.now(),
        type: 'sale',
        ticket_id,
        start,
        terminal,
        train_type,
        cost,
        station_code,
        device,
        trips_total,
        trips_remaining
    };

    await DataService.appendTicketEvent(ev);
    await DataService.upsertTicketIndex({
        ticket_id,
        start,
        terminal,
        train_type,
        cost,
        status: 'valid',
        station_code,
        last_event: 'sale',
        start_name: b.start_name,
        terminal_name: b.terminal_name,
        start_en: b.start_name_en,
        terminal_en: b.terminal_name_en,
        trips_total,
        trips_remaining,
        last_update_ts: Date.now()
    });

    const now = new Date();
    const statItem = {
        device,
        station_code,
        sold_tickets: 1,
        revenue: cost,
        ts: Date.now(),
        window_hour: now.getHours().toString().padStart(2, '0'),
        window_day: now.toISOString().split('T')[0],
        type: 'ticket'
    };
    await DataService.appendStatTicket(statItem);

    io.emit('ticket:sale', ev);
    io.emit('stats:ticket:updated', statItem);
    appendReqLog(req, { category: 'device', type: 'ticket_sale', detail: ev });
    res.json({ ok: true, ticket_id });
});

// Ticket Search
router.get('/tickets', (req, res) => {
    const q = String(req.query.q || '').trim().toLowerCase();
    const idx = DataService.getTicketIndex();
    let list = Object.entries(idx).map(([ticket_id, data]) => ({ ticket_id, ...data }));
    if (q) {
        list = list.filter(t => 
            String(t.ticket_id || '').toLowerCase().includes(q) || 
            String(t.station_code || '').toLowerCase().includes(q) || 
            String(t.start || '').toLowerCase().includes(q) || 
            String(t.terminal || '').toLowerCase().includes(q)
        );
    }
    list.sort((a, b) => Number(b.last_update_ts || 0) - Number(a.last_update_ts || 0));
    list = list.map(t => {
        const tripsRemaining = (t.trips_remaining ?? t.rides_remaining);
        const tripsTotal = (t.trips_total ?? t.rides_total);
        const shouldBeUsed =
            (typeof tripsRemaining === 'number' && tripsRemaining <= 0) ||
            ((tripsTotal == null || Number(tripsTotal) <= 1) && String(t.last_action || '') === 'exit');

        const status = (t.status && t.status !== 'valid') ? t.status : (shouldBeUsed ? 'used' : (t.status || 'valid'));
        return { ...t, status };
    });
    
    // Format to Chinese keys as expected by ticket-search.js
    const map = LogicService.buildStationNameMap();
    const nameFor = (code) => (map && map[code]) || code;
    
    const stations = DataService.getStations();
    const enNameFor = (code) => {
        const s = stations.find(x => x.code === code);
        return s ? (s.en_name || s.enName || '') : '';
    };
    
    const formatted = list.map(t => ({
        ticket_id: t.ticket_id,
        start_name: t.start ? nameFor(t.start) : (t.start_name || ''),
        start_code: t.start || '',
        start_en: t.start ? enNameFor(t.start) : (t.start_en || ''),
        terminal_name: t.terminal ? nameFor(t.terminal) : (t.terminal_name || ''),
        terminal_code: t.terminal || '',
        terminal_en: t.terminal ? enNameFor(t.terminal) : (t.terminal_en || ''),
        train_type: t.train_type || '',
        station_name: t.station_name || nameFor(t.station_code),
        station_code: t.station_code || '',
        trips_total: t.trips_total ?? 0,
        trips_remaining: t.trips_remaining ?? null,
        amount: t.cost ?? 0,
        status: t.status || '',
        last_event: t.last_event || '',
        last_action: t.last_action || '',
        last_station: nameFor(t.last_station_code),
        last_update_ts: t.last_update_ts || 0
    }));
    
    appendReqLog(req, { category: 'public', type: 'ticket_search', detail: { q, count: formatted.length } });
    res.json(formatted);
});

router.get('/tickets/:id', async (req, res, next) => {
    try {
        const rawId = String(req.params.id || '').trim();
        const id0 = normalizeTicketId(rawId);
        if (!rawId) return res.status(400).json({ error: 'ticket_id_required' });

        const idx = DataService.getTicketIndex();
        const candidates = Array.from(new Set([
            rawId, rawId.toUpperCase(), rawId.toLowerCase(),
            id0, id0.toUpperCase(), id0.toLowerCase()
        ])).filter(Boolean);
        let id = id0 || rawId;
        let t = null;
        for (const c of candidates) {
            if (idx[c]) { id = c; t = idx[c]; break; }
        }
        if (!t) {
            const targetNorms = new Set(candidates.map(x => normalizeTicketId(x)).filter(Boolean));
            for (const k of Object.keys(idx || {})) {
                const nk = normalizeTicketId(k);
                if (!nk) continue;
                if (targetNorms.has(nk) || targetNorms.has(String(nk).toUpperCase()) || targetNorms.has(String(nk).toLowerCase())) {
                    id = k;
                    t = idx[k];
                    break;
                }
            }
        }
        if (!t) {
            appendReqLog(req, { category: 'public', type: 'ticket_detail', detail: { ticket_id: (id0 || rawId), ok: false } });
            return res.status(404).json({ ticket_id: (id0 || rawId), overview: null, events: [] });
        }
        const tripsRemaining = (t.trips_remaining ?? t.rides_remaining);
        const tripsTotal = (t.trips_total ?? t.rides_total);
        const shouldBeUsed =
            (typeof tripsRemaining === 'number' && tripsRemaining <= 0) ||
            ((tripsTotal == null || Number(tripsTotal) <= 1) && String(t.last_action || '') === 'exit');
        const status = (t.status && t.status !== 'valid') ? t.status : (shouldBeUsed ? 'used' : (t.status || 'valid'));

        const allEvents = await DataService.readAllTicketEvents();
        const events = (Array.isArray(allEvents) ? allEvents : []).filter(e => e && e.ticket_id === id);
        const map = LogicService.buildStationNameMap();
        const nameFor = (code) => (map && map[code]) || code;

        const stations = DataService.getStations() || [];
        const enNameFor = (code) => {
            const s = stations.find(x => x.code === code);
            return s ? (s.en_name || s.enName || '') : '';
        };

        const overview = {
            ticket_id: id,
            start_name: t.start ? nameFor(t.start) : (t.start_name || ''),
            start_code: t.start || '',
            start_en: t.start ? enNameFor(t.start) : (t.start_en || ''),
            terminal_name: t.terminal ? nameFor(t.terminal) : (t.terminal_name || ''),
            terminal_code: t.terminal || '',
            terminal_en: t.terminal ? enNameFor(t.terminal) : (t.terminal_en || ''),
            train_type: t.train_type || '',
            station_name: t.station_name || nameFor(t.station_code),
            station_code: t.station_code || '',
            trips_total: t.trips_total ?? 0,
            trips_remaining: t.trips_remaining ?? null,
            amount: t.cost ?? 0,
            status,
            last_event: t.last_event || '',
            last_action: t.last_action || '',
            last_station: nameFor(t.last_station_code),
            last_update_ts: t.last_update_ts || 0
        };

        const formattedEvents = events.map(e => {
            if (e.type === 'sale') {
                return {
                    type: 'sale',
                    ts: e.ts,
                    station_name: e.station_name || nameFor(e.station_code),
                    station_code: e.station_code || '',
                    station_en: e.station_code ? enNameFor(e.station_code) : (e.station_en || ''),
                    ticket_id: e.ticket_id,
                    start_name: nameFor(e.start),
                    terminal_name: nameFor(e.terminal),
                    train_type: e.train_type || '',
                    trips_total: e.trips_total ?? 0,
                    amount: e.cost ?? 0
                };
            } else if (e.type === 'status') {
                return {
                    type: 'status',
                    action: e.action,
                    ts: e.ts,
                    ticket_id: e.ticket_id,
                    station_name: nameFor(e.station_code),
                    station_code: e.station_code || '',
                    station_en: e.station_code ? enNameFor(e.station_code) : (e.station_en || ''),
                    trips_remaining: e.trips_remaining ?? e.rides_remaining ?? null
                };
            }
            return { raw: e };
        });

        appendReqLog(req, { category: 'public', type: 'ticket_detail', detail: { ticket_id: id, ok: true } });
        res.json({ ticket_id: id, overview, events: formattedEvents });
    } catch (err) {
        appendReqLog(req, { category: 'system', type: 'ticket_detail_failed', level: 'error', detail: { error: err?.message || String(err) } });
        next(err);
    }
});

router.get('/popular', async (req, res) => {
    const events = (await DataService.readAllTicketEvents()).filter(e => e && e.type === 'sale');
    const cntStation = new Map();
    const cntRoute = new Map();
    for (const e of events) {
        const k1 = e.start || ''; const k2 = e.terminal || '';
        if (k1) cntStation.set(k1, (cntStation.get(k1) || 0) + 1);
        if (k2) cntStation.set(k2, (cntStation.get(k2) || 0) + 1);
        if (k1 && k2) { const key = `${k1}|${k2}`; cntRoute.set(key, (cntRoute.get(key) || 0) + 1); }
    }
    const map = LogicService.buildStationNameMap();
    const nameByCode = map;
    const topStations = Array.from(cntStation.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([code, count]) => ({ name: nameByCode[code] || code, code, count }));
    const topRoutes = Array.from(cntRoute.entries()).sort((a, b) => b[1] - a[1]).slice(0, 10).map(([key, count]) => { const [from, to] = key.split('|'); return { from: nameByCode[from] || from, to: nameByCode[to] || to, count }; });
    res.json({ ok: true, topStations, topRoutes });
});

router.get('/fares/map/light', (req, res) => {
    const stationTransfers = [];
    for (const s of (DataService.getStations() || [])) {
        const from = String(s?.code || '').trim();
        if (!from) continue;
        if (!s?.transfer_enabled) continue;
        const toList = Array.isArray(s.transfer_to) ? s.transfer_to : [];
        for (const t of toList) {
            const to = String((t && typeof t === 'object') ? (t.code || t.station || t.id || '') : t).trim();
            if (!to || to === from) continue;
            stationTransfers.push([from, to]);
        }
    }
    const mergedTransfers = [
        ...((DataService.getConfig().transfers || []).filter(x => Array.isArray(x) && x.length >= 2)),
        ...stationTransfers
    ];
    const svg = svgGenerator.generate(
        DataService.getStations(),
        DataService.getLines(),
        DataService.getFares(),
        LogicService.buildStationNameMap(),
        mergedTransfers
    );
    res.set('Content-Type', 'image/svg+xml; charset=utf-8');
    res.set('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
    res.set('Pragma', 'no-cache');
    res.set('Expires', '0');
    res.send(svg);
});

module.exports = router;
