const crypto = require('crypto');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
const { computeEffectiveAccess } = require('@/services/platform/accessControl');
const shortid = require('shortid');
const checkAndCorrectURL = require('@/controllers/middlewaresControllers/createAuthMiddleware/checkAndCorrectURL');
const { useAppSettings } = require('@/settings');

const createTemporaryPassword = () => crypto.randomBytes(9).toString('base64url');

const resolveBaseUrl = (req) => {
  const settings = useAppSettings();
  const configured =
    settings?.idurar_base_url || process.env.CLIENT_URL || process.env.FRONTEND_URL || process.env.APP_URL;

  if (configured) {
    return checkAndCorrectURL(String(configured));
  }

  const origin = req.headers.origin || req.headers.referer;
  if (origin) {
    try {
      return new URL(origin).origin;
    } catch (_error) {
      return String(origin).replace(/\/+$/, '');
    }
  }

  const protocol =
    req.headers['x-forwarded-proto'] ||
    (req.secure ? 'https' : 'http');
  const host = req.headers['x-forwarded-host'] || req.get('host');
  return `${protocol}://${host}`;
};

const buildSetupLink = ({ req, userId, resetToken }) =>
  `${resolveBaseUrl(req)}/resetpassword/${userId}/${resetToken}`;

const serializeSession = (token, index) => {
  const decoded = jwt.decode(token) || {};
  const issuedAt = decoded.iat ? new Date(decoded.iat * 1000) : null;
  const expiresAt = decoded.exp ? new Date(decoded.exp * 1000) : null;

  return {
    id: `${decoded.id || 'session'}-${decoded.iat || index}`,
    tokenPreview: typeof token === 'string' ? `${token.slice(0, 8)}...${token.slice(-6)}` : `session-${index + 1}`,
    issuedAt,
    expiresAt,
    active: !expiresAt || expiresAt > new Date(),
  };
};

const preparePasswordSetup = async ({ AdminPassword, userId, temporaryPassword }) => {
  const salt = crypto.randomBytes(8).toString('hex');
  const resetToken = shortid.generate();
  const passwordRecord = new AdminPassword();
  const hashedPassword = passwordRecord.generateHash(salt, temporaryPassword);

  await AdminPassword.findOneAndUpdate(
    { user: userId },
    {
      $set: {
        password: hashedPassword,
        salt,
        authType: 'email',
        resetToken,
        emailVerified: false,
      },
      $setOnInsert: {
        loggedSessions: [],
      },
    },
    { upsert: true, new: true }
  );

  return { resetToken };
};

const list = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AuditLog = mongoose.model('AuditLog');
  const admins = await Admin.find({ tenant: req.tenantId, removed: false })
    .sort({ created: -1 })
    .lean();

  const result = await Promise.all(
    admins.map(async (admin) => {
      const recentActivity = await AuditLog.find({ tenant: req.tenantId, actor: admin._id })
        .sort({ created: -1 })
        .limit(5)
        .lean();

      return {
        ...admin,
        effectiveAccess: computeEffectiveAccess(admin),
        recentActivity,
      };
    })
  );

  return res.status(200).json({ success: true, result, message: 'Users loaded' });
};

const create = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AdminPassword = mongoose.model('AdminPassword');
  const existing = await Admin.findOne({ tenant: req.tenantId, email: req.body?.email, removed: false });
  if (existing) {
    return res.status(400).json({ success: false, result: null, message: 'A user with this email already exists' });
  }

  const result = await Admin.create({
    tenant: req.tenantId,
    createdBy: req.admin?._id,
    assignedTo: req.admin?._id,
    email: req.body?.email,
    name: req.body?.name,
    surname: req.body?.surname,
    phone: req.body?.phone,
    orgUnit: req.body?.orgUnit || undefined,
    manager: req.body?.manager || undefined,
    roleRefs: req.body?.roleRefs || [],
    permissions: req.body?.permissions || [],
    deniedFields: req.body?.deniedFields || [],
    fieldPermissions: req.body?.fieldPermissions || [],
    branches: req.body?.branches || [],
    enabled: req.body?.enabled !== false,
  });

  const tempPassword = req.body?.temporaryPassword || createTemporaryPassword();
  const { resetToken } = await preparePasswordSetup({
    AdminPassword,
    userId: result._id,
    temporaryPassword: tempPassword,
  });

  return res.status(201).json({
    success: true,
    result: {
      ...result.toObject(),
      temporaryPassword: tempPassword,
      resetToken,
      setupLink: buildSetupLink({ req, userId: result._id, resetToken }),
      passwordSetupRequired: true,
    },
    message: 'User created',
  });
};

const update = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const result = await Admin.findOneAndUpdate(
    { _id: req.params.id, tenant: req.tenantId, removed: false },
    {
      $set: {
        name: req.body?.name,
        surname: req.body?.surname,
        phone: req.body?.phone,
        manager: req.body?.manager || undefined,
        orgUnit: req.body?.orgUnit || undefined,
        roleRefs: req.body?.roleRefs || [],
        permissions: req.body?.permissions || [],
        deniedFields: req.body?.deniedFields || [],
        fieldPermissions: req.body?.fieldPermissions || [],
        branches: req.body?.branches || [],
        enabled: req.body?.enabled !== false,
      },
    },
    { new: true }
  );

  return res.status(200).json({ success: true, result, message: 'User updated' });
};

const resetPassword = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AdminPassword = mongoose.model('AdminPassword');
  const admin = await Admin.findOne({ _id: req.params.id, tenant: req.tenantId, removed: false });
  if (!admin) {
    return res.status(404).json({ success: false, result: null, message: 'User not found' });
  }

  const nextPassword = req.body?.temporaryPassword || createTemporaryPassword();
  const { resetToken } = await preparePasswordSetup({
    AdminPassword,
    userId: admin._id,
    temporaryPassword: nextPassword,
  });

  return res.status(200).json({
    success: true,
    result: {
      _id: admin._id,
      temporaryPassword: nextPassword,
      resetToken,
      setupLink: buildSetupLink({ req, userId: admin._id, resetToken }),
      passwordSetupRequired: true,
    },
    message: 'Password reset',
  });
};

const sessions = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AdminPassword = mongoose.model('AdminPassword');

  const admin = await Admin.findOne({ _id: req.params.id, tenant: req.tenantId, removed: false }).lean();
  if (!admin) {
    return res.status(404).json({ success: false, result: null, message: 'User not found' });
  }

  const passwordRecord = await AdminPassword.findOne({ user: admin._id, removed: false }).lean();
  const sessions = (passwordRecord?.loggedSessions || []).map(serializeSession);

  return res.status(200).json({
    success: true,
    result: {
      user: {
        _id: admin._id,
        name: admin.name,
        surname: admin.surname,
        email: admin.email,
      },
      sessions,
    },
    message: 'User sessions loaded',
  });
};

const revokeSession = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AdminPassword = mongoose.model('AdminPassword');

  const admin = await Admin.findOne({ _id: req.params.id, tenant: req.tenantId, removed: false }).lean();
  if (!admin) {
    return res.status(404).json({ success: false, result: null, message: 'User not found' });
  }

  const passwordRecord = await AdminPassword.findOne({ user: admin._id, removed: false });
  if (!passwordRecord) {
    return res.status(404).json({ success: false, result: null, message: 'User password record not found' });
  }

  const sessionId = req.params.sessionId;
  const nextSessions = (passwordRecord.loggedSessions || []).filter((token, index) => serializeSession(token, index).id !== sessionId);
  passwordRecord.loggedSessions = nextSessions;
  await passwordRecord.save();

  return res.status(200).json({
    success: true,
    result: { sessions: nextSessions.map(serializeSession) },
    message: 'Session revoked',
  });
};

const revokeAllSessions = async (req, res) => {
  const Admin = mongoose.model('Admin');
  const AdminPassword = mongoose.model('AdminPassword');

  const admin = await Admin.findOne({ _id: req.params.id, tenant: req.tenantId, removed: false }).lean();
  if (!admin) {
    return res.status(404).json({ success: false, result: null, message: 'User not found' });
  }

  await AdminPassword.findOneAndUpdate(
    { user: admin._id, removed: false },
    { $set: { loggedSessions: [] } },
    { new: true }
  );

  return res.status(200).json({
    success: true,
    result: { sessions: [] },
    message: 'All sessions revoked',
  });
};

module.exports = {
  list,
  create,
  update,
  resetPassword,
  sessions,
  revokeSession,
  revokeAllSessions,
};
