import { Router, Request, Response, NextFunction } from 'express';
import { createClient } from '@supabase/supabase-js';
import { query, queryOne } from '../db';
import { requireAuth } from '../middleware/auth';
import { inviteLimiter, strictActionLimiter } from '../middleware/rateLimit';
import { logUserActivity } from '../services/activityLog';
import { createTransporter, getDefaultFrom } from '../mailer';
import { logger } from '../logger';

const router = Router();

function supabaseAdmin() {
  return createClient(
    process.env.SUPABASE_URL || 'http://ec-kong:8000',
    process.env.SUPABASE_SERVICE_ROLE_KEY || '',
    { auth: { autoRefreshToken: false, persistSession: false } }
  );
}

function clientIp(req: Request): string | undefined {
  const x = req.headers['x-forwarded-for'];
  const first = typeof x === 'string' ? x.split(',')[0]?.trim() : undefined;
  return first || req.ip || undefined;
}

function frontendPasswordResetUrl(): string {
  const port = process.env.FRONTEND_PORT || '8098';
  const defaultOrigin = `http://localhost:${port}`;
  const base = (process.env.SITE_URL || process.env.FRONTEND_URL || defaultOrigin).replace(/\/$/, '');
  return `${base}/reset-password`;
}

// All /api/users routes require a valid session; most require admin
router.use(requireAuth());

// GET /api/users/me/activity — current user's activity (must be before /:id routes)
router.get('/me/activity', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const limit = Math.min(100, Math.max(1, parseInt(String(req.query.limit || '50'), 10) || 50));
    const offset = Math.max(0, parseInt(String(req.query.offset || '0'), 10) || 0);
    const uid = req.authUser!.id;
    const rows = await query<{
      id: string;
      action: string;
      metadata: Record<string, unknown>;
      ip_address: string | null;
      created_at: string;
    }>(
      `SELECT id, action, metadata, ip_address, created_at
       FROM user_activity_log WHERE user_id = $1
       ORDER BY created_at DESC LIMIT $2 OFFSET $3`,
      [uid, limit, offset]
    );
    res.json(rows);
  } catch (err) { next(err); }
});

// PATCH /api/users/me — update own profile (display name, avatar URL)
router.patch('/me', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const { full_name, avatar_url } = req.body as { full_name?: unknown; avatar_url?: unknown };
    const uid = req.authUser!.id;

    let fn: string | null | undefined;
    if (full_name !== undefined) {
      if (full_name !== null && typeof full_name !== 'string') {
        return res.status(400).json({ error: 'full_name must be a string or null' });
      }
      fn = full_name === null || full_name === '' ? null : full_name.slice(0, 200);
    }

    let av: string | null | undefined;
    if (avatar_url !== undefined) {
      if (avatar_url !== null && typeof avatar_url !== 'string') {
        return res.status(400).json({ error: 'avatar_url must be a string or null' });
      }
      if (typeof avatar_url === 'string' && avatar_url.length > 0) {
        try {
          const u = new URL(avatar_url);
          if (u.protocol !== 'http:' && u.protocol !== 'https:') {
            return res.status(400).json({ error: 'avatar_url must be http(s)' });
          }
        } catch {
          return res.status(400).json({ error: 'avatar_url must be a valid URL' });
        }
      }
      av = avatar_url === null || avatar_url === '' ? null : avatar_url.slice(0, 2000);
    }

    if (fn === undefined && av === undefined) {
      return res.status(400).json({ error: 'No updates: send full_name and/or avatar_url' });
    }

    const sets: string[] = ['updated_at = now()'];
    const params: unknown[] = [];
    let i = 1;
    if (fn !== undefined) {
      sets.push(`full_name = $${i++}`);
      params.push(fn);
    }
    if (av !== undefined) {
      sets.push(`avatar_url = $${i++}`);
      params.push(av);
    }
    params.push(uid);

    const updated = await queryOne<{
      id: string;
      email: string;
      role: string;
      full_name: string | null;
      avatar_url: string | null;
      created_at: string;
      updated_at: string;
    }>(
      `UPDATE profiles SET ${sets.join(', ')} WHERE id = $${i} RETURNING id, email, role, full_name, avatar_url, created_at, updated_at`,
      params
    );
    if (!updated) return res.status(404).json({ error: 'Profile not found' });

    await logUserActivity(
      uid,
      'profile_updated',
      { fields: [...(fn !== undefined ? ['full_name'] : []), ...(av !== undefined ? ['avatar_url'] : [])] },
      clientIp(req)
    );

    res.json(updated);
  } catch (err) { next(err); }
});

// GET /api/users/me — current user's profile
router.get('/me', async (req: Request, res: Response, next: NextFunction) => {
  try {
    const row = await queryOne<{
      id: string;
      email: string;
      role: string;
      full_name: string | null;
      avatar_url: string | null;
      created_at: string;
      updated_at: string;
    }>(
      `SELECT id, email, role, full_name, avatar_url, created_at, updated_at FROM profiles WHERE id = $1`,
      [req.authUser!.id]
    );
    res.json(row ?? req.authUser);
  } catch (err) { next(err); }
});

// GET /api/users — list all users with their profiles (admin only)
router.get('/', requireAuth('admin'), async (_req: Request, res: Response, next: NextFunction) => {
  try {
    const { data, error } = await supabaseAdmin().auth.admin.listUsers({ page: 1, perPage: 500 });
    if (error) throw error;

    const profiles = await query<{
      id: string;
      role: string;
      full_name: string | null;
      updated_at: string;
    }>(
      'SELECT id, role, full_name, updated_at FROM profiles'
    );
    const profileMap = Object.fromEntries(profiles.map(p => [p.id, p]));

    const users = data.users.map(u => ({
      id: u.id,
      email: u.email,
      full_name: profileMap[u.id]?.full_name ?? null,
      role: profileMap[u.id]?.role ?? 'user',
      created_at: u.created_at,
      last_sign_in_at: u.last_sign_in_at,
      email_confirmed_at: u.email_confirmed_at,
    }));

    res.json(users);
  } catch (err) { next(err); }
});

// POST /api/users/invite — create a new user (admin only)
router.post('/invite', inviteLimiter, requireAuth('admin'), async (req: Request, res: Response, next: NextFunction) => {
  try {
    const { email, password, role = 'user' } = req.body;
    if (!email || !password) return res.status(400).json({ error: 'email and password are required' });
    if (!['admin', 'user'].includes(role)) return res.status(400).json({ error: 'role must be admin or user' });

    const { data, error } = await supabaseAdmin().auth.admin.createUser({
      email,
      password,
      email_confirm: true,
    });
    if (error) throw error;

    // Set role in profiles (trigger creates it as 'user'; update if admin)
    await queryOne(
      "INSERT INTO profiles (id, email, role) VALUES ($1,$2,$3) ON CONFLICT (id) DO UPDATE SET role=$3, updated_at=now()",
      [data.user.id, email, role]
    );

    await logUserActivity(
      data.user.id,
      'user_invited',
      { email, role, invited_by: req.authUser!.id },
      clientIp(req)
    );
    await logUserActivity(req.authUser!.id, 'admin_invited_user', { target_id: data.user.id, email }, clientIp(req));

    res.status(201).json({ id: data.user.id, email, role });
  } catch (err) { next(err); }
});

// GET /api/users/:id/activity — another user's activity (admin only)
router.get('/:id/activity', requireAuth('admin'), async (req: Request, res: Response, next: NextFunction) => {
  try {
    const limit = Math.min(100, Math.max(1, parseInt(String(req.query.limit || '50'), 10) || 50));
    const offset = Math.max(0, parseInt(String(req.query.offset || '0'), 10) || 0);
    const targetId = req.params.id;
    const rows = await query<{
      id: string;
      action: string;
      metadata: Record<string, unknown>;
      ip_address: string | null;
      created_at: string;
    }>(
      `SELECT id, action, metadata, ip_address, created_at
       FROM user_activity_log WHERE user_id = $1
       ORDER BY created_at DESC LIMIT $2 OFFSET $3`,
      [targetId, limit, offset]
    );
    res.json(rows);
  } catch (err) { next(err); }
});

// POST /api/users/:id/password-reset — email a recovery link (admin only; uses app SMTP)
router.post(
  '/:id/password-reset',
  strictActionLimiter,
  requireAuth('admin'),
  async (req: Request, res: Response, next: NextFunction) => {
    try {
      const targetId = req.params.id;
      const { data: authData, error: getErr } = await supabaseAdmin().auth.admin.getUserById(targetId);
      if (getErr || !authData.user?.email) {
        return res.status(404).json({ error: 'User not found' });
      }
      const email = authData.user.email;

      const redirectTo = frontendPasswordResetUrl();
      const { data: linkData, error: linkErr } = await supabaseAdmin().auth.admin.generateLink({
        type: 'recovery',
        email,
        options: { redirectTo },
      });
      if (linkErr) throw linkErr;

      const actionLink =
        (linkData?.properties as { action_link?: string } | undefined)?.action_link
        ?? (linkData as { action_link?: string } | undefined)?.action_link;
      if (!actionLink) {
        logger.error({ linkData }, 'generateLink missing action_link');
        return res.status(500).json({ error: 'Could not create recovery link' });
      }

      const from = await getDefaultFrom();
      const transporter = await createTransporter();
      await transporter.sendMail({
        from: `"${from.name}" <${from.email}>`,
        to: email,
        subject: 'Reset your Email Campaigner password',
        text: `Open this link to choose a new password (expires soon):\n\n${actionLink}\n\nIf you did not ask for this, you can ignore this email.`,
        html: `<p>You requested a password reset for your Email Campaigner account.</p>
<p><a href="${actionLink}">Set a new password</a></p>
<p>If the button does not work, paste this URL into your browser:<br/><code style="word-break:break-all">${actionLink}</code></p>
<p>If you did not ask for this, you can ignore this email.</p>`,
      });

      await logUserActivity(
        targetId,
        'password_reset_email_sent',
        { sent_by: req.authUser!.id },
        clientIp(req)
      );
      await logUserActivity(
        req.authUser!.id,
        'admin_sent_password_reset',
        { target_id: targetId, email },
        clientIp(req)
      );

      res.json({ ok: true, message: 'Recovery email sent.' });
    } catch (err) { next(err); }
  }
);

// PATCH /api/users/:id/role — change a user's role (admin only)
router.patch('/:id/role', requireAuth('admin'), async (req: Request, res: Response, next: NextFunction) => {
  try {
    const { role } = req.body;
    if (!['admin', 'user'].includes(role)) return res.status(400).json({ error: 'role must be admin or user' });

    // Prevent demoting yourself
    if (req.params.id === req.authUser!.id && role !== 'admin') {
      return res.status(400).json({ error: 'Cannot change your own role' });
    }

    const updated = await queryOne<{ id: string; email: string; role: string }>(
      "UPDATE profiles SET role=$2, updated_at=now() WHERE id=$1 RETURNING id, email, role",
      [req.params.id, role]
    );
    if (!updated) return res.status(404).json({ error: 'User not found' });

    await logUserActivity(
      req.params.id,
      'role_changed',
      { new_role: role, changed_by: req.authUser!.id },
      clientIp(req)
    );

    res.json(updated);
  } catch (err) { next(err); }
});

// DELETE /api/users/:id — delete a user entirely (admin only)
router.delete('/:id', requireAuth('admin'), async (req: Request, res: Response, next: NextFunction) => {
  try {
    if (req.params.id === req.authUser!.id) {
      return res.status(400).json({ error: 'Cannot delete your own account' });
    }

    const targetId = req.params.id;
    await logUserActivity(req.authUser!.id, 'admin_deleted_user', { target_id: targetId }, clientIp(req));

    const { error } = await supabaseAdmin().auth.admin.deleteUser(targetId);
    if (error) throw error;

    // Profile is deleted by cascade (FK profiles.id → auth.users.id)
    res.status(204).send();
  } catch (err) { next(err); }
});

export default router;
