import { Router } from 'express';
import { v4 as uuidv4 } from 'uuid';
import { rateLimit } from 'express-rate-limit';
import { getDb } from '../config/database.js';
import { authenticateToken, getBaseUrl } from '../middleware/auth.js';
import { sendFederationInviteEmail, sendCalendarEventDeletedEmail } from '../config/mailer.js';
import { log } from '../config/logger.js';
import { createNotification } from '../config/notifications.js';
import { getAppName } from '../config/appName.js';

// M13: rate limit the unauthenticated federation receive endpoint
const federationReceiveLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100,
    standardHeaders: true,
    legacyHeaders: false,
    message: { error: 'Too many federation requests. Please try again later.' },
});

import {
    getFederationDomain,
    isFederationEnabled,
    getPublicKey,
    signPayload,
    verifyPayload,
    discoverInstance,
    parseAddress,
} from '../config/federation.js';

const router = Router();

// ── Well-known discovery endpoint ───────────────────────────────────────────
// Mounted at /.well-known/redlight in index.js
export function wellKnownHandler(req, res) {
    const domain = getFederationDomain();
    if (!domain) {
        return res.status(404).json({ error: 'Federation not configured' });
    }
    res.json({
        domain,
        federation_api: '/api/federation',
        public_key: getPublicKey(),
        software: 'Redlight',
        version: '2.5.0',
    });
}

// ── POST /api/federation/invite — Send invitation to remote user ────────────
router.post('/invite', authenticateToken, async (req, res) => {
    try {
        if (!isFederationEnabled()) {
            return res.status(400).json({ error: 'Federation is not configured on this instance' });
        }

        const { room_uid, to, message } = req.body;
        if (!room_uid || !to) {
            return res.status(400).json({ error: 'room_uid and to are required' });
        }

        const { username, domain } = parseAddress(to);
        if (!domain) {
            return res.status(400).json({ error: 'Remote address must be in format username@domain' });
        }

        // Don't allow inviting to own instance
        if (domain === getFederationDomain()) {
            return res.status(400).json({ error: 'Cannot send federation invite to your own instance. Use local sharing instead.' });
        }

        const db = getDb();

        // Verify room exists and user has access
        const room = await db.get('SELECT * FROM rooms WHERE uid = ?', [room_uid]);
        if (!room) {
            return res.status(404).json({ error: 'Room not found' });
        }

        const isOwner = room.user_id === req.user.id;
        if (!isOwner) {
            const share = await db.get('SELECT id FROM room_shares WHERE room_id = ? AND user_id = ?', [room.id, req.user.id]);
            if (!share) {
                return res.status(403).json({ error: 'No permission to invite from this room' });
            }
        }

        // Build guest join URL for the remote user
        // If the room has an access code, embed it so the recipient can join without manual entry
        const baseUrl = getBaseUrl(req);
        const joinUrl = room.access_code
            ? `${baseUrl}/join/${room.uid}?ac=${encodeURIComponent(room.access_code)}`
            : `${baseUrl}/join/${room.uid}`;

        // Build invitation payload
        const inviteId = uuidv4();
        const payload = {
            invite_id: inviteId,
            from_user: `@${req.user.name}@${getFederationDomain()}`,
            to_user: to,
            room_name: room.name,
            room_uid: room.uid,
            max_participants: room.max_participants ?? 0,
            allow_recording: room.record_meeting ?? 1,
            message: message || null,
            join_url: joinUrl,
            timestamp: new Date().toISOString(),
        };

        // Sign and send to remote instance
        const signature = signPayload(payload);
        const { baseUrl: remoteApi } = await discoverInstance(domain);

        const response = await fetch(`${remoteApi}/receive`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'X-Federation-Signature': signature,
                'X-Federation-Origin': getFederationDomain(),
            },
            body: JSON.stringify(payload),
            signal: AbortSignal.timeout(15_000), // 15 second timeout
        });

        if (!response.ok) {
            const data = await response.json().catch(() => ({}));
            throw new Error(data.error || `Remote server responded with ${response.status}`);
        }

        // Track outbound invite for deletion propagation
        try {
            await db.run(
                `INSERT INTO federation_outbound_invites (room_uid, remote_domain) VALUES (?, ?)
                 ON CONFLICT(room_uid, remote_domain) DO NOTHING`,
                [room.uid, domain]
            );
        } catch { /* table may not exist yet on upgrade */ }

        res.json({ success: true, invite_id: inviteId });
    } catch (err) {
        log.federation.error('Federation invite error:', err);
        res.status(500).json({ error: err.message || 'Failed to send federation invite' });
    }
});

// ── POST /api/federation/receive — Accept incoming invitation from remote ───
router.post('/receive', federationReceiveLimiter, async (req, res) => {
    try {
        if (!isFederationEnabled()) {
            return res.status(400).json({ error: 'Federation is not configured on this instance' });
        }

        const signature = req.headers['x-federation-signature'];
        const payload = req.body || {};

        if (!signature) {
            return res.status(401).json({ error: 'Missing federation signature' });
        }

        // Extract expected fields from the incoming payload
        const { invite_id, from_user, to_user, room_name, room_uid, max_participants, allow_recording, message, join_url } = payload;

        if (!invite_id || !from_user || !to_user || !room_name || !join_url) {
            return res.status(400).json({ error: 'Incomplete invitation payload' });
        }

        // Validate join_url scheme to prevent javascript: or other malicious URIs
        try {
            const parsedUrl = new URL(join_url);
            if (parsedUrl.protocol !== 'https:' && parsedUrl.protocol !== 'http:') {
                return res.status(400).json({ error: 'join_url must use https:// or http://' });
            }
        } catch {
            return res.status(400).json({ error: 'Invalid join_url format' });
        }

        // S4: validate field lengths from remote to prevent oversized DB entries
        if (invite_id.length > 100 || from_user.length > 200 || to_user.length > 200 ||
            room_name.length > 200 || join_url.length > 2000 || (message && message.length > 5000)) {
            return res.status(400).json({ error: 'Payload fields exceed maximum allowed length' });
        }

        // Fetch the sender's public key dynamically
        const { domain: senderDomain } = parseAddress(from_user);
        if (!senderDomain) {
            return res.status(400).json({ error: 'Sender address must include a domain' });
        }

        const { publicKey } = await discoverInstance(senderDomain);
        if (!publicKey) {
            return res.status(400).json({ error: 'Sender instance did not provide a public key' });
        }

        if (!verifyPayload(payload, signature, publicKey)) {
            return res.status(403).json({ error: 'Invalid federation signature' });
        }

        // Parse the target address and find local user
        const { username } = parseAddress(to_user);
        const db = getDb();

        // Look up user by name (case-insensitive)
        const targetUser = await db.get(
            'SELECT id, name, email, language FROM users WHERE LOWER(name) = LOWER(?)',
            [username]
        );

        if (!targetUser) {
            return res.status(404).json({ error: 'User not found on this instance' });
        }

        // Check for duplicate
        const existing = await db.get(
            'SELECT id FROM federation_invitations WHERE invite_id = ?',
            [invite_id]
        );
        if (existing) {
            return res.json({ success: true, message: 'Invitation already received' });
        }

        // Store the invitation
        await db.run(
            `INSERT INTO federation_invitations (invite_id, from_user, to_user_id, room_name, message, join_url)
       VALUES (?, ?, ?, ?, ?, ?)`,
            [invite_id, from_user, targetUser.id, room_name, message || null, join_url]
        );

        // Store room_uid, max_participants, allow_recording if those columns already exist
        // (we update after initial insert to stay compatible with old schema)
        const inv = await db.get('SELECT id FROM federation_invitations WHERE invite_id = ? AND to_user_id = ?', [invite_id, targetUser.id]);
        if (inv && room_uid !== undefined) {
            try {
                await db.run(
                    'UPDATE federation_invitations SET room_uid = ?, max_participants = ?, allow_recording = ? WHERE id = ?',
                    [room_uid || null, max_participants ?? 0, allow_recording ?? 1, inv.id]
                );
            } catch { /* column may not exist on very old installs */ }
        }

        // Send notification email (truly fire-and-forget - never blocks the response)
        if (targetUser.email) {
            const appUrl = getBaseUrl(req);
            const inboxUrl = `${appUrl}/federation/inbox`;
            const appName = await getAppName();
            sendFederationInviteEmail(
                targetUser.email, targetUser.name, from_user,
                room_name, message || null, inboxUrl, appName, targetUser.language || 'en'
            ).catch(mailErr => {
                log.federation.warn('Federation invite mail failed (non-fatal):', mailErr.message);
            });
        }

        // In-app notification
        await createNotification(
            targetUser.id,
            'federation_invite_received',
            from_user,
            room_name,
            '/federation/inbox',
        );

        res.json({ success: true });
    } catch (err) {
        log.federation.error('Federation receive error:', err);
        res.status(500).json({ error: 'Failed to process federation invitation' });
    }
});

// ── GET /api/federation/invitations — List invitations for current user ─────
router.get('/invitations', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const invitations = await db.all(
            `SELECT * FROM federation_invitations
       WHERE to_user_id = ?
       ORDER BY created_at DESC`,
            [req.user.id]
        );
        res.json({ invitations });
    } catch (err) {
        log.federation.error('List invitations error:', err);
        res.status(500).json({ error: 'Failed to load invitations' });
    }
});

// ── GET /api/federation/invitations/pending-count — Badge count ─────────────
router.get('/invitations/pending-count', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const roomResult = await db.get(
            `SELECT COUNT(*) as count FROM federation_invitations
       WHERE to_user_id = ? AND status = 'pending'`,
            [req.user.id]
        );
        let calResult = { count: 0 };
        try {
            calResult = await db.get(
                `SELECT COUNT(*) as count FROM calendar_invitations
         WHERE to_user_id = ? AND status = 'pending'`,
                [req.user.id]
            );
        } catch { /* table may not exist yet */ }
        let localCalResult = { count: 0 };
        try {
            localCalResult = await db.get(
                `SELECT COUNT(*) as count FROM calendar_local_invitations
         WHERE to_user_id = ? AND status = 'pending'`,
                [req.user.id]
            );
        } catch { /* table may not exist yet */ }
        res.json({ count: (roomResult?.count || 0) + (calResult?.count || 0) + (localCalResult?.count || 0) });
    } catch (err) {
        res.json({ count: 0 });
    }
});

// ── POST /api/federation/invitations/:id/accept — Accept an invitation ──────
router.post('/invitations/:id/accept', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const invitation = await db.get(
            'SELECT * FROM federation_invitations WHERE id = ? AND to_user_id = ?',
            [req.params.id, req.user.id]
        );

        if (!invitation) {
            return res.status(404).json({ error: 'Invitation not found' });
        }

        await db.run(
            "UPDATE federation_invitations SET status = 'accepted' WHERE id = ?",
            [invitation.id]
        );

        // Upsert into federated_rooms so the room appears in the user's dashboard
        const existing = await db.get(
            'SELECT id FROM federated_rooms WHERE invite_id = ? AND user_id = ?',
            [invitation.invite_id, req.user.id]
        );
        if (!existing) {
            await db.run(
                `INSERT INTO federated_rooms (user_id, invite_id, room_name, from_user, join_url, meet_id, max_participants, allow_recording)
         VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
                [
                    req.user.id, invitation.invite_id, invitation.room_name,
                    invitation.from_user, invitation.join_url,
                    invitation.room_uid || null,
                    invitation.max_participants ?? 0,
                    invitation.allow_recording ?? 1,
                ]
            );
        }

        res.json({ success: true, join_url: invitation.join_url });
    } catch (err) {
        log.federation.error('Accept invitation error:', err);
        res.status(500).json({ error: 'Failed to accept invitation' });
    }
});

// ── DELETE /api/federation/invitations/:id — Decline/dismiss invitation ─────
router.delete('/invitations/:id', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const invitation = await db.get(
            'SELECT * FROM federation_invitations WHERE id = ? AND to_user_id = ?',
            [req.params.id, req.user.id]
        );

        if (!invitation) {
            return res.status(404).json({ error: 'Invitation not found' });
        }

        await db.run('DELETE FROM federation_invitations WHERE id = ?', [invitation.id]);

        res.json({ success: true });
    } catch (err) {
        log.federation.error('Decline invitation error:', err);
        res.status(500).json({ error: 'Failed to decline invitation' });
    }
});

// ── GET /api/federation/calendar-invitations — List calendar invitations ─────
router.get('/calendar-invitations', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const invitations = await db.all(
            `SELECT * FROM calendar_invitations
       WHERE to_user_id = ?
       ORDER BY created_at DESC`,
            [req.user.id]
        );
        res.json({ invitations });
    } catch (err) {
        log.federation.error('List calendar invitations error:', err);
        res.status(500).json({ error: 'Failed to load calendar invitations' });
    }
});

// ── POST /api/federation/calendar-invitations/:id/accept ─────────────────────
router.post('/calendar-invitations/:id/accept', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const inv = await db.get(
            `SELECT * FROM calendar_invitations WHERE id = ? AND to_user_id = ?`,
            [req.params.id, req.user.id]
        );
        if (!inv) return res.status(404).json({ error: 'Calendar invitation not found' });
        if (inv.status === 'accepted') return res.status(400).json({ error: 'Already accepted' });

        await db.run(
            `UPDATE calendar_invitations SET status = 'accepted' WHERE id = ?`,
            [inv.id]
        );

        // Check if event was already previously accepted (duplicate guard)
        const existing = await db.get(
            'SELECT id FROM calendar_events WHERE uid = ? AND user_id = ?',
            [inv.event_uid, req.user.id]
        );
        if (!existing) {
            await db.run(`
              INSERT INTO calendar_events (uid, title, description, start_time, end_time, room_uid, user_id, color, federated_from, federated_join_url)
              VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            `, [
                inv.event_uid,
                inv.title,
                inv.description || null,
                inv.start_time,
                inv.end_time,
                inv.room_uid || null,
                req.user.id,
                inv.color || '#6366f1',
                inv.from_user,
                inv.join_url || null,
            ]);
        }

        res.json({ success: true });
    } catch (err) {
        log.federation.error('Accept calendar invitation error:', err);
        res.status(500).json({ error: 'Failed to accept calendar invitation' });
    }
});

// ── DELETE /api/federation/calendar-invitations/:id — Decline/dismiss ────────
router.delete('/calendar-invitations/:id', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const inv = await db.get(
            `SELECT * FROM calendar_invitations WHERE id = ? AND to_user_id = ?`,
            [req.params.id, req.user.id]
        );
        if (!inv) return res.status(404).json({ error: 'Calendar invitation not found' });

        if (inv.status === 'pending') {
            // mark as declined
            await db.run(`UPDATE calendar_invitations SET status = 'declined' WHERE id = ?`, [inv.id]);
        } else {
            // accepted or declined — permanently remove from inbox
            await db.run('DELETE FROM calendar_invitations WHERE id = ?', [inv.id]);
        }

        res.json({ success: true });
    } catch (err) {
        log.federation.error('Delete calendar invitation error:', err);
        res.status(500).json({ error: 'Failed to remove calendar invitation' });
    }
});

// ── GET /api/federation/federated-rooms — List saved federated rooms ────────
router.get('/federated-rooms', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const rooms = await db.all(
            `SELECT * FROM federated_rooms WHERE user_id = ? ORDER BY created_at DESC`,
            [req.user.id]
        );
        res.json({ rooms });
    } catch (err) {
        log.federation.error('List federated rooms error:', err);
        res.status(500).json({ error: 'Failed to load federated rooms' });
    }
});

// ── DELETE /api/federation/federated-rooms/:id — Remove a federated room ────
router.delete('/federated-rooms/:id', authenticateToken, async (req, res) => {
    try {
        const db = getDb();
        const room = await db.get(
            'SELECT * FROM federated_rooms WHERE id = ? AND user_id = ?',
            [req.params.id, req.user.id]
        );
        if (!room) return res.status(404).json({ error: 'Room not found' });
        await db.run('DELETE FROM federated_rooms WHERE id = ?', [room.id]);
        res.json({ success: true });
    } catch (err) {
        log.federation.error('Delete federated room error:', err);
        res.status(500).json({ error: 'Failed to remove room' });
    }
});

// ── POST /api/federation/room-sync — Remote instances query room settings ───
// Called by federated instances to pull current room info for one or more UIDs.
// Signed request from remote, no auth token needed.
router.post('/room-sync', federationReceiveLimiter, async (req, res) => {
    try {
        if (!isFederationEnabled()) {
            return res.status(400).json({ error: 'Federation is not configured on this instance' });
        }

        const signature = req.headers['x-federation-signature'];
        const originDomain = req.headers['x-federation-origin'];
        const payload = req.body || {};

        if (!signature || !originDomain) {
            return res.status(401).json({ error: 'Missing federation signature or origin' });
        }

        // Verify signature using the remote instance's public key
        const { publicKey } = await discoverInstance(originDomain);
        if (!publicKey || !verifyPayload(payload, signature, publicKey)) {
            return res.status(403).json({ error: 'Invalid federation signature' });
        }

        const { room_uids } = payload;
        if (!Array.isArray(room_uids) || room_uids.length === 0 || room_uids.length > 100) {
            return res.status(400).json({ error: 'room_uids must be an array of 1-100 UIDs' });
        }

        const db = getDb();
        const result = {};

        for (const uid of room_uids) {
            if (typeof uid !== 'string' || uid.length > 100) continue;
            const room = await db.get('SELECT uid, name, max_participants, record_meeting FROM rooms WHERE uid = ?', [uid]);
            if (room) {
                result[uid] = {
                    room_name: room.name,
                    max_participants: room.max_participants ?? 0,
                    allow_recording: room.record_meeting ?? 1,
                    deleted: false,
                };
            } else {
                result[uid] = { deleted: true };
            }
        }

        res.json({ rooms: result });
    } catch (err) {
        log.federation.error('Room-sync error:', err);
        res.status(500).json({ error: 'Failed to process room sync request' });
    }
});

// ── POST /api/federation/calendar-event-deleted — Receive calendar deletion ─
router.post('/calendar-event-deleted', federationReceiveLimiter, async (req, res) => {
    try {
        if (!isFederationEnabled()) {
            return res.status(400).json({ error: 'Federation is not configured on this instance' });
        }

        const signature = req.headers['x-federation-signature'];
        const originDomain = req.headers['x-federation-origin'];
        const payload = req.body || {};

        if (!signature || !originDomain) {
            return res.status(401).json({ error: 'Missing federation signature or origin' });
        }

        const { publicKey } = await discoverInstance(originDomain);
        if (!publicKey || !verifyPayload(payload, signature, publicKey)) {
            return res.status(403).json({ error: 'Invalid federation signature' });
        }

        const { event_uid } = payload;
        if (!event_uid || typeof event_uid !== 'string') {
            return res.status(400).json({ error: 'event_uid is required' });
        }

        const db = getDb();

        // Escape LIKE special characters in originDomain to prevent wildcard injection.
        const safeDomain = originDomain.replace(/[\\%_]/g, '\\$&');

        // Collect all affected users before deleting (for email notifications)
        let affectedUsers = [];
        try {
            // Users with pending/declined invitations
            const invUsers = await db.all(
                `SELECT u.email, u.name, u.language, ci.title, ci.from_user
                 FROM calendar_invitations ci
                 JOIN users u ON ci.to_user_id = u.id
                 WHERE ci.event_uid = ? AND ci.from_user LIKE ? ESCAPE '\\'`,
                [event_uid, `%@${safeDomain}`]
            );
            // Users who already accepted (event in their calendar)
            const calUsers = await db.all(
                `SELECT u.email, u.name, u.language, ce.title, ce.federated_from AS from_user
                 FROM calendar_events ce
                 JOIN users u ON ce.user_id = u.id
                 WHERE ce.uid = ? AND ce.federated_from LIKE ? ESCAPE '\\'`,
                [event_uid, `%@${safeDomain}`]
            );
            // Merge, deduplicate by email
            const seen = new Set();
            for (const row of [...invUsers, ...calUsers]) {
                if (row.email && !seen.has(row.email)) {
                    seen.add(row.email);
                    affectedUsers.push(row);
                }
            }
        } catch { /* non-fatal */ }

        // Remove from calendar_invitations for all users on this instance
        await db.run(
            `DELETE FROM calendar_invitations
             WHERE event_uid = ? AND from_user LIKE ? ESCAPE '\\'`,
            [event_uid, `%@${safeDomain}`]
        );

        // Remove from calendar_events (accepted invitations) for all users on this instance
        await db.run(
            `DELETE FROM calendar_events
             WHERE uid = ? AND federated_from LIKE ? ESCAPE '\\'`,
            [event_uid, `%@${safeDomain}`]
        );

        log.federation.info(`Calendar event ${event_uid} deleted (origin: ${originDomain})`);

        // Notify affected users by email (fire-and-forget)
        if (affectedUsers.length > 0) {
            const appName = await getAppName();
            for (const u of affectedUsers) {
                sendCalendarEventDeletedEmail(u.email, u.name, u.from_user, u.title, appName, u.language || 'en')
                    .catch(mailErr => {
                        log.federation.warn(`Calendar deletion mail to ${u.email} failed (non-fatal): ${mailErr.message}`);
                    });
            }
        }

        res.json({ success: true });
    } catch (err) {
        log.federation.error('Calendar-event-deleted error:', err);
        res.status(500).json({ error: 'Failed to process calendar event deletion' });
    }
});

// ── POST /api/federation/room-deleted — Receive deletion notification ───────
// Origin instance pushes this to notify that a room has been deleted.
router.post('/room-deleted', federationReceiveLimiter, async (req, res) => {
    try {
        if (!isFederationEnabled()) {
            return res.status(400).json({ error: 'Federation is not configured on this instance' });
        }

        const signature = req.headers['x-federation-signature'];
        const originDomain = req.headers['x-federation-origin'];
        const payload = req.body || {};

        if (!signature || !originDomain) {
            return res.status(401).json({ error: 'Missing federation signature or origin' });
        }

        const { publicKey } = await discoverInstance(originDomain);
        if (!publicKey || !verifyPayload(payload, signature, publicKey)) {
            return res.status(403).json({ error: 'Invalid federation signature' });
        }

        const { room_uid } = payload;
        if (!room_uid || typeof room_uid !== 'string') {
            return res.status(400).json({ error: 'room_uid is required' });
        }

        const db = getDb();
        // Escape LIKE special characters in originDomain to prevent wildcard injection.
        const safeDomain = originDomain.replace(/[\\%_]/g, '\\$&');
        // Mark all federated_rooms with this meet_id (room_uid) from this origin as deleted
        await db.run(
            `UPDATE federated_rooms SET deleted = 1, updated_at = CURRENT_TIMESTAMP
             WHERE meet_id = ? AND from_user LIKE ? ESCAPE '\\'`,
            [room_uid, `%@${safeDomain}`]
        );

        log.federation.info(`Room ${room_uid} marked as deleted (origin: ${originDomain})`);
        res.json({ success: true });
    } catch (err) {
        log.federation.error('Room-deleted error:', err);
        res.status(500).json({ error: 'Failed to process deletion notification' });
    }
});

export default router;
