import { NextFunction, Request, Response } from 'express';
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import { RowDataPacket } from 'mysql2';
import { pool } from '../config/db.js';
import { AuthRequest } from '../middleware/auth.middleware.js';
import { getRolePermissions, unrestrictedPermissions } from '../services/permissionService.js';

interface UserRow extends RowDataPacket {
  id: number;
  name: string;
  email: string;
  password_hash: string;
  is_active: number;
}

interface MeRow extends RowDataPacket {
  id: number;
  name: string;
  email: string;
  designation: string | null;
  mobile_number: string | null;
  photo_path: string | null;
  role_id: number | null;
  role_name: string | null;
  is_active: number;
}

interface UnitRow extends RowDataPacket {
  id: number;
  name: string;
}

interface DivisionRow extends RowDataPacket {
  id: number;
  name: string;
  unit_id: number;
}

const ME_SELECT = `
  SELECT u.id, u.name, u.email, u.designation, u.mobile_number, u.photo_path,
         u.role_id, r.name AS role_name, u.is_active
  FROM users u
  LEFT JOIN roles r ON r.id = u.role_id
`;

async function profileFor(user: MeRow) {
  const [unitRows] = await pool.query<UnitRow[]>(
    `SELECT un.id, un.name FROM user_units uu JOIN units un ON un.id = uu.unit_id WHERE uu.user_id = ? ORDER BY un.name`,
    [user.id],
  );
  const [divisionRows] = await pool.query<DivisionRow[]>(
    `SELECT d.id, d.name, d.unit_id FROM user_divisions ud JOIN divisions d ON d.id = ud.division_id WHERE ud.user_id = ? ORDER BY d.name`,
    [user.id],
  );
  return {
    id: user.id,
    name: user.name,
    email: user.email,
    designation: user.designation,
    mobileNumber: user.mobile_number,
    photoUrl: user.photo_path,
    roleName: user.role_name,
    units: unitRows,
    divisions: divisionRows,
    isActive: !!user.is_active,
  };
}

export async function login(req: Request, res: Response, next: NextFunction) {
  const { email, password } = req.body as { email?: string; password?: string };
  if (!email || !password) {
    return res.status(400).json({ message: 'Email and password are required' });
  }

  try {
    const [rows] = await pool.query<UserRow[]>(
      'SELECT id, name, email, password_hash, is_active FROM users WHERE email = ? LIMIT 1',
      [email],
    );
    const user = rows[0];
    if (!user || !user.is_active) {
      return res.status(401).json({ message: 'Invalid email or password' });
    }

    const valid = await bcrypt.compare(password, user.password_hash);
    if (!valid) {
      return res.status(401).json({ message: 'Invalid email or password' });
    }

    const token = jwt.sign({ sub: user.id }, process.env.JWT_SECRET!, { expiresIn: '7d' });
    const [meRows] = await pool.query<MeRow[]>(`${ME_SELECT} WHERE u.id = ? LIMIT 1`, [user.id]);
    const me = meRows[0];
    if (!me) {
      return res.status(401).json({ message: 'Unauthorized' });
    }
    const permissions = me.role_id ? await getRolePermissions(me.role_id) : unrestrictedPermissions();

    res.json({
      token,
      user: await profileFor(me),
      permissions,
    });
  } catch (err) {
    next(err);
  }
}

export async function me(req: AuthRequest, res: Response, next: NextFunction) {
  try {
    const [rows] = await pool.query<MeRow[]>(`${ME_SELECT} WHERE u.id = ? LIMIT 1`, [req.userId]);
    const user = rows[0];
    if (!user || !user.is_active) {
      return res.status(401).json({ message: 'Unauthorized' });
    }

    const permissions = user.role_id ? await getRolePermissions(user.role_id) : unrestrictedPermissions();
    res.json({
      user: await profileFor(user),
      permissions,
    });
  } catch (err) {
    next(err);
  }
}

export async function changePassword(req: AuthRequest, res: Response, next: NextFunction) {
  const { currentPassword, newPassword } = req.body as { currentPassword?: string; newPassword?: string };
  if (!currentPassword || !newPassword) {
    return res.status(400).json({ message: 'Current password and new password are required' });
  }
  if (newPassword.length < 6) {
    return res.status(400).json({ message: 'New password must be at least 6 characters' });
  }

  try {
    const [rows] = await pool.query<UserRow[]>(
      'SELECT id, password_hash FROM users WHERE id = ? LIMIT 1',
      [req.userId],
    );
    const user = rows[0];
    if (!user) {
      return res.status(404).json({ message: 'User not found' });
    }

    const valid = await bcrypt.compare(currentPassword, user.password_hash);
    if (!valid) {
      return res.status(400).json({ message: 'Current password is incorrect' });
    }

    const passwordHash = await bcrypt.hash(newPassword, 10);
    await pool.query('UPDATE users SET password_hash = ? WHERE id = ?', [passwordHash, req.userId]);
    res.json({ message: 'Password changed successfully' });
  } catch (err) {
    next(err);
  }
}
