import { Router, Request, Response } from 'express';
import crypto from 'crypto';
import nodemailer from 'nodemailer';
import { ResultSetHeader, RowDataPacket } from 'mysql2';
import authPool from '../config/authDb';
import { verifyCodeLimiter, verifyCodeSlowDown } from '../middlewares/security';

const router = Router();

const CODE_TTL_MS = 10 * 60 * 1000;
const SESSION_TTL_HOURS = Math.min(Number(process.env.AUTH_SESSION_TTL_HOURS || 12), 24);
const SESSION_TTL_MS = SESSION_TTL_HOURS * 60 * 60 * 1000;
const RESEND_COOLDOWN_MS = 60 * 1000;
const MAX_ATTEMPTS = 5;
const HASH_PEPPER = process.env.AUTH_HASH_PEPPER || 'change-this-pepper';

const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const usernameRegex = /^(?=.{3,24}$)[a-zA-Z0-9._-]+$/;

const normalizeEmail = (email: string) => email.trim().toLowerCase();

const hashCode = (email: string, code: string) => {
    return crypto.createHash('sha256').update(`${email}:${code}:${HASH_PEPPER}`).digest('hex');
};

const normalizeUsername = (username: string) => username.trim().toLowerCase();

const hashSessionToken = (token: string) => {
    return crypto.createHash('sha256').update(`${token}:${HASH_PEPPER}`).digest('hex');
};

const resolveAuthDbError = (error: any) => {
    const code = String(error?.code || '');
    if (code === 'ETIMEDOUT' || code === 'ECONNREFUSED' || code === 'PROTOCOL_CONNECTION_LOST') {
        return { status: 503, message: 'Base auth indisponible.' };
    }
    if (code === 'ER_ACCESS_DENIED_ERROR' || code === 'ER_BAD_DB_ERROR') {
        return { status: 503, message: 'Configuration DB auth invalide.' };
    }
    if (code === 'ER_NO_SUCH_TABLE') {
        return { status: 503, message: 'Tables auth manquantes.' };
    }
    return null;
};

const cleanupExpiredEntries = async () => {
    await authPool.query('DELETE FROM auth_code_challenges WHERE expires_at <= NOW() OR consumed_at IS NOT NULL');
    await authPool.query('DELETE FROM auth_verified_sessions WHERE expires_at <= NOW() OR revoked_at IS NOT NULL');
};

const getTransporter = () => {
    const host = process.env.MAIL_HOST;
    const port = Number(process.env.MAIL_PORT || 587);
    const secure = String(process.env.MAIL_SECURE || 'false') === 'true';
    const user = process.env.MAIL_USER;
    const pass = process.env.MAIL_PASS;

    if (!host || !user || !pass) {
        return null;
    }

    return nodemailer.createTransport({
        host,
        port,
        secure,
        connectionTimeout: Number(process.env.MAIL_CONNECTION_TIMEOUT_MS) || 4000,
        greetingTimeout: Number(process.env.MAIL_GREETING_TIMEOUT_MS) || 4000,
        socketTimeout: Number(process.env.MAIL_SOCKET_TIMEOUT_MS) || 6000,
        auth: {
            user,
            pass
        }
    });
};

router.post('/send-code', async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.body?.email || '');
    const email = normalizeEmail(incomingEmail);

    if (!email || !emailRegex.test(email)) {
        res.status(400).json({ status: 'error', message: 'Email invalide.' });
        return;
    }

    const transporter = getTransporter();
    if (!transporter) {
        res.status(500).json({ status: 'error', message: 'SMTP non configuré.' });
        return;
    }

    try {
        await cleanupExpiredEntries();

        const [existingRows] = await authPool.query<RowDataPacket[]>(
            `SELECT id, last_sent_at
             FROM auth_code_challenges
             WHERE email = ? AND consumed_at IS NULL AND expires_at > NOW()
             ORDER BY id DESC
             LIMIT 1`,
            [email]
        );

        if (existingRows.length > 0) {
            const lastSent = new Date(existingRows[0].last_sent_at as string | Date).getTime();
            const remainingMs = RESEND_COOLDOWN_MS - (Date.now() - lastSent);
            if (remainingMs > 0) {
                res.status(429).json({
                    status: 'error',
                    message: 'Veuillez patienter avant de demander un nouveau code.',
                    retryAfterSeconds: Math.ceil(remainingMs / 1000)
                });
                return;
            }
        }

        const code = String(crypto.randomInt(100000, 1000000));
        const codeHash = hashCode(email, code);
        const fromAddress = process.env.MAIL_FROM || process.env.MAIL_USER || 'no-reply@xeewy.be';
        const subject = process.env.MAIL_AUTH_SUBJECT || 'Votre code de verification';
        const text = `Votre code de verification est: ${code}\nCe code expire dans 10 minutes.`;

        await transporter.sendMail({
            from: fromAddress,
            to: email,
            subject,
            text
        });

        await authPool.query(
            `INSERT INTO auth_code_challenges (email, code_hash, attempts, expires_at, last_sent_at)
             VALUES (?, ?, 0, DATE_ADD(NOW(), INTERVAL ? SECOND), NOW())`,
            [email, codeHash, Math.floor(CODE_TTL_MS / 1000)]
        );

        res.json({ status: 'success', message: 'Code envoyé.' });
    } catch (error) {
        console.error('Failed to send auth code email:', error);
        res.status(500).json({ status: 'error', message: "Impossible d'envoyer le code." });
    }
});

router.get('/username-available', async (req: Request, res: Response): Promise<void> => {
    const incomingUsername = String(req.query.username || '');
    const username = normalizeUsername(incomingUsername);

    if (!usernameRegex.test(username)) {
        res.status(400).json({
            status: 'error',
            message: 'Pseudo invalide (3-24, lettres/chiffres/._-).'
        });
        return;
    }

    try {
        const [rows] = await authPool.query<RowDataPacket[]>(
            'SELECT id FROM auth_users WHERE username = ? LIMIT 1',
            [username]
        );

        res.json({
            status: 'success',
            available: rows.length === 0
        });
    } catch (error) {
        console.error('Failed to check username availability:', error);
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

router.get('/profile-status', async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.query.email || '');
    const email = normalizeEmail(incomingEmail);

    if (!email || !emailRegex.test(email)) {
        res.status(400).json({ status: 'error', message: 'Email invalide.' });
        return;
    }

    try {
        const [rows] = await authPool.query<RowDataPacket[]>(
            'SELECT id, username FROM auth_users WHERE email = ? LIMIT 1',
            [email]
        );

        if (rows.length === 0) {
            res.json({ status: 'success', hasProfile: false });
            return;
        }

        res.json({
            status: 'success',
            hasProfile: true,
            username: String(rows[0].username || '')
        });
    } catch (error) {
        console.error('Failed to get profile status:', error);
        const dbError = resolveAuthDbError(error);
        if (dbError) {
            res.status(dbError.status).json({ status: 'error', message: dbError.message });
            return;
        }
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

router.post('/register-profile', async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.body?.email || '');
    const incomingUid = String(req.body?.firebaseUid || '');
    const incomingUsername = String(req.body?.username || '');

    const email = normalizeEmail(incomingEmail);
    const firebaseUid = incomingUid.trim();
    const username = normalizeUsername(incomingUsername);

    if (!email || !emailRegex.test(email)) {
        res.status(400).json({ status: 'error', message: 'Email invalide.' });
        return;
    }

    if (!firebaseUid || firebaseUid.length < 10) {
        res.status(400).json({ status: 'error', message: 'UID Firebase invalide.' });
        return;
    }

    if (!usernameRegex.test(username)) {
        res.status(400).json({
            status: 'error',
            message: 'Pseudo invalide (3-24, lettres/chiffres/._-).'
        });
        return;
    }

    try {
        const [existingByEmail] = await authPool.query<RowDataPacket[]>(
            'SELECT id, username FROM auth_users WHERE email = ? LIMIT 1',
            [email]
        );

        if (existingByEmail.length > 0) {
            const currentUsername = String(existingByEmail[0].username || '');
            if (currentUsername === username) {
                res.json({ status: 'success', message: 'Profil déjà enregistré.' });
                return;
            }

            res.status(409).json({
                status: 'error',
                message: 'Un profil existe déjà pour cet email avec un autre pseudo.'
            });
            return;
        }

        await authPool.query<ResultSetHeader>(
            'INSERT INTO auth_users (firebase_uid, email, username) VALUES (?, ?, ?)',
            [firebaseUid, email, username]
        );

        res.status(201).json({ status: 'success', message: 'Profil enregistré.' });
    } catch (error: any) {
        if (error?.code === 'ER_DUP_ENTRY') {
            res.status(409).json({ status: 'error', message: 'Ce pseudo est déjà utilisé.' });
            return;
        }
        console.error('Failed to register auth profile:', error);
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

router.post('/verify-code', verifyCodeSlowDown, verifyCodeLimiter, async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.body?.email || '');
    const incomingCode = String(req.body?.code || '').trim();
    const email = normalizeEmail(incomingEmail);

    if (!email || !emailRegex.test(email) || !/^\d{6}$/.test(incomingCode)) {
        res.status(400).json({ status: 'error', message: 'Données invalides.' });
        return;
    }

    try {
        await cleanupExpiredEntries();

        const [rows] = await authPool.query<RowDataPacket[]>(
            `SELECT id, code_hash, attempts, expires_at
             FROM auth_code_challenges
             WHERE email = ? AND consumed_at IS NULL
             ORDER BY id DESC
             LIMIT 1`,
            [email]
        );

        if (rows.length === 0) {
            res.status(404).json({ status: 'error', message: 'Aucun code actif pour cet email.' });
            return;
        }

        const challenge = rows[0];
        const challengeId = Number(challenge.id);
        const attempts = Number(challenge.attempts || 0);
        const expiresAt = new Date(challenge.expires_at as string | Date).getTime();
        const storedHash = String(challenge.code_hash || '');

        if (expiresAt <= Date.now()) {
            await authPool.query('UPDATE auth_code_challenges SET consumed_at = NOW() WHERE id = ?', [challengeId]);
            res.status(410).json({ status: 'error', message: 'Code expiré.' });
            return;
        }

        if (attempts >= MAX_ATTEMPTS) {
            await authPool.query('UPDATE auth_code_challenges SET consumed_at = NOW() WHERE id = ?', [challengeId]);
            res.status(429).json({ status: 'error', message: 'Trop de tentatives.' });
            return;
        }

        const providedHash = hashCode(email, incomingCode);
        const validCode = crypto.timingSafeEqual(Buffer.from(storedHash), Buffer.from(providedHash));

        if (!validCode) {
            await authPool.query('UPDATE auth_code_challenges SET attempts = attempts + 1 WHERE id = ?', [challengeId]);
            res.status(401).json({ status: 'error', message: 'Code invalide.' });
            return;
        }

        await authPool.query('UPDATE auth_code_challenges SET consumed_at = NOW() WHERE id = ?', [challengeId]);

        const token = crypto.randomBytes(32).toString('hex');
        const tokenHash = hashSessionToken(token);

        await authPool.query(
            `INSERT INTO auth_verified_sessions (token_hash, email, expires_at)
             VALUES (?, ?, DATE_ADD(NOW(), INTERVAL ? SECOND))`,
            [tokenHash, email, Math.floor(SESSION_TTL_MS / 1000)]
        );

        res.json({
            status: 'success',
            token,
            expiresAt: Date.now() + SESSION_TTL_MS
        });
    } catch (error) {
        console.error('Failed to verify code:', error);
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

router.post('/check-session', async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.body?.email || '');
    const token = String(req.body?.token || '').trim();
    const email = normalizeEmail(incomingEmail);

    if (!email || !emailRegex.test(email) || !token) {
        res.status(400).json({ status: 'error', message: 'Données invalides.' });
        return;
    }

    try {
        await cleanupExpiredEntries();

        const tokenHash = hashSessionToken(token);
        const [rows] = await authPool.query<RowDataPacket[]>(
            `SELECT id
             FROM auth_verified_sessions
             WHERE token_hash = ? AND email = ? AND revoked_at IS NULL AND expires_at > NOW()
             LIMIT 1`,
            [tokenHash, email]
        );

        if (rows.length === 0) {
            res.status(401).json({ status: 'error', message: 'Session invalide.' });
            return;
        }

        res.json({ status: 'success' });
    } catch (error) {
        console.error('Failed to check session:', error);
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

router.post('/logout-session', async (req: Request, res: Response): Promise<void> => {
    const incomingEmail = String(req.body?.email || '');
    const token = String(req.body?.token || '').trim();
    const email = normalizeEmail(incomingEmail);

    if (!email || !emailRegex.test(email) || !token) {
        res.status(400).json({ status: 'error', message: 'Données invalides.' });
        return;
    }

    try {
        const tokenHash = hashSessionToken(token);
        await authPool.query(
            `UPDATE auth_verified_sessions
             SET revoked_at = NOW()
             WHERE token_hash = ? AND email = ? AND revoked_at IS NULL`,
            [tokenHash, email]
        );
        res.json({ status: 'success' });
    } catch (error) {
        console.error('Failed to revoke session:', error);
        res.status(500).json({ status: 'error', message: 'Erreur serveur.' });
    }
});

export default router;
