import { Request, Response } from 'express';
import jwt from 'jsonwebtoken';
import { User } from '../models/User';
import { Company } from '../models/Company';
import { Project } from '../models/Project';
import { JWT_CONFIG } from '../config/jwt';
import { AuditLog } from '../models/AuditLog';
import { AuthRequest } from '../middleware/auth.middleware';
import mongoose from 'mongoose';
import { attachUserRoleFields } from '../utils/attachUserRoleFields';
import { isCsmEnabled } from '../csm/config';
import { verifyCsmCredentials } from '../csm/auth';
import { mapCsmRoleToAomCode, buildCsmSessionUser } from '../csm/mapUser';

/** Expose whether CSM delegation is active, so the frontend can go read-only. */
export const getAuthConfig = (_req: Request, res: Response): void => {
  res.json({ success: true, data: { csmEnabled: isCsmEnabled() } });
};

/**
 * Login by delegating credential check to CSM (plan §9.2/§9.6/§9.7).
 * AOM still issues its OWN JWT; the token carries the AOM role code + csm flag so
 * the authenticate middleware can trust it without a local users lookup.
 */
async function loginViaCsm(req: Request, res: Response, email: string, password: string): Promise<void> {
  const normalizedEmail = String(email).toLowerCase().trim();

  let result;
  try {
    result = await verifyCsmCredentials(normalizedEmail, password);
  } catch (error: any) {
    console.error('[Auth/CSM] verify failed:', error?.message || error);
    res.status(503).json({
      success: false,
      message: 'Hệ thống xác thực CSM tạm thời không phản hồi. Vui lòng thử lại sau.',
    });
    return;
  }

  if (!result.valid) {
    res.status(401).json({ success: false, message: 'Email hoặc mật khẩu không đúng' });
    return;
  }

  // enforceGrant (strict mode) on CSM: valid password but not granted access to AOM.
  if (result.access === false) {
    res.status(403).json({
      success: false,
      message: 'Tài khoản hợp lệ nhưng chưa được cấp quyền truy cập AOM. Vui lòng liên hệ quản trị CSM.',
    });
    return;
  }

  const csmUser = result.user || ({ id: normalizedEmail, email: normalizedEmail, name: normalizedEmail } as any);
  const roleCode = mapCsmRoleToAomCode(csmUser, result.webtool?.roles);
  const userId = String(csmUser.id || normalizedEmail);
  const name = csmUser.name || normalizedEmail;

  const token = jwt.sign(
    { userId, email: normalizedEmail, role: roleCode, name, csm: true },
    JWT_CONFIG.secret,
    { expiresIn: JWT_CONFIG.expiresIn } as jwt.SignOptions
  );

  try {
    await AuditLog.create({
      userId,
      action: 'LOGIN',
      targetCollection: 'users',
      targetId: userId,
      details: `CSM login: ${normalizedEmail} (role ${roleCode})`,
      ipAddress: req.ip || 'unknown',
      userAgent: req.get('user-agent') || 'unknown',
    });
  } catch (auditError: any) {
    console.error('[Auth/CSM] Failed to create audit log:', auditError.message);
  }

  const userResponse = buildCsmSessionUser(csmUser, roleCode);
  await attachUserRoleFields(userResponse as any);

  console.log(`[Auth/CSM] Login successful: ${normalizedEmail} -> ${roleCode}`);
  res.json({ success: true, data: { token, user: userResponse } });
}

export const login = async (req: Request, res: Response): Promise<void> => {
  try {
    const { email, password } = req.body;

    if (!email || !password) {
      res.status(400).json({
        success: false,
        message: 'Email and password are required',
      });
      return;
    }

    // When CSM delegation is enabled, verify against CSM instead of local bcrypt.
    if (isCsmEnabled()) {
      await loginViaCsm(req, res, email, password);
      return;
    }

    // Find user with password field - use lean() to get plain object
    const normalizedEmail = email.toLowerCase().trim();
    let user;
    let userDoc;

    try {
      // First try with lean() to get plain object (handles string _id better)
      userDoc = await User.findOne({ email: normalizedEmail }).select('+password').lean();

      if (!userDoc) {
        console.log(`[Auth] Login attempt failed: User not found - ${normalizedEmail}`);
        res.status(401).json({
          success: false,
          message: 'Email hoặc mật khẩu không đúng',
        });
        return;
      }

      // Get user as Mongoose document for password comparison
      user = await User.findOne({ email: normalizedEmail }).select('+password');

      if (!user) {
        console.log(`[Auth] Login attempt failed: User not found (document) - ${normalizedEmail}`);
        res.status(401).json({
          success: false,
          message: 'Email hoặc mật khẩu không đúng',
        });
        return;
      }
    } catch (queryError: any) {
      console.error(`[Auth] Error querying user: ${normalizedEmail}`, queryError);
      console.error('[Auth] Query error stack:', queryError.stack);
      res.status(500).json({
        success: false,
        message: 'Internal server error',
      });
      return;
    }

    // Debug: Log user _id type
    console.log(`[Auth] User found: ${normalizedEmail}, _id type: ${typeof userDoc._id}, _id value: ${userDoc._id}`);

    if (user.status !== 'Active') {
      console.log(`[Auth] Login attempt failed: Account inactive - ${normalizedEmail}`);
      res.status(401).json({
        success: false,
        message: 'Tài khoản đã bị vô hiệu hóa',
      });
      return;
    }

    // Check if user has password set
    if (!user.password) {
      console.log(`[Auth] Login attempt failed: User has no password set - ${normalizedEmail}`);
      res.status(401).json({
        success: false,
        message: 'Tài khoản chưa được thiết lập mật khẩu. Vui lòng liên hệ quản trị viên.',
      });
      return;
    }

    // Check password
    const isPasswordValid = await user.comparePassword(password);

    if (!isPasswordValid) {
      console.log(`[Auth] Login attempt failed: Invalid password - ${normalizedEmail}`);
      res.status(401).json({
        success: false,
        message: 'Email hoặc mật khẩu không đúng',
      });
      return;
    }

    console.log(`[Auth] Login successful: ${normalizedEmail} (${user.role})`);

    // Get user ID from userDoc (plain object) which has the actual _id from MongoDB
    // Use userDoc._id instead of user._id because Mongoose document may not handle string _id properly
    const userId = userDoc._id
      ? (typeof userDoc._id === 'string' ? userDoc._id : String(userDoc._id))
      : null;

    if (!userId) {
      console.error(`[Auth] User ID is missing for user: ${normalizedEmail}`);
      console.error(`[Auth] userDoc._id: ${userDoc._id}, type: ${typeof userDoc._id}`);
      console.error(`[Auth] user._id: ${user._id}, type: ${typeof user._id}`);
      res.status(500).json({
        success: false,
        message: 'Internal server error',
      });
      return;
    }

    console.log(`[Auth] Using userId: ${userId} (type: ${typeof userId})`);

    // Generate JWT token
    const token = jwt.sign(
      { userId: userId, email: user.email },
      JWT_CONFIG.secret,
      { expiresIn: JWT_CONFIG.expiresIn } as jwt.SignOptions
    );

    // Create audit log (wrap in try-catch to prevent login failure if audit log fails)
    try {
      await AuditLog.create({
        userId: userId,
        action: 'LOGIN',
        targetCollection: 'users',
        targetId: userId,
        details: `User logged in: ${user.email}`,
        ipAddress: req.ip || 'unknown',
        userAgent: req.get('user-agent') || 'unknown',
      });
    } catch (auditError: any) {
      // Log but don't fail login if audit log fails
      console.error('[Auth] Failed to create audit log:', auditError.message);
    }

    // Return user without password - use userDoc (plain object from lean())
    // This avoids issues with Mongoose document conversion when _id is string
    const userResponse: any = { ...userDoc };

    // Remove password if present
    if (userResponse.password) {
      delete userResponse.password;
    }

    // Ensure _id is string for consistency
    if (userResponse._id) {
      userResponse._id = typeof userResponse._id === 'string'
        ? userResponse._id
        : userResponse._id.toString();
    }

    await attachUserRoleFields(userResponse);

    res.json({
      success: true,
      data: {
        token,
        user: userResponse,
      },
    });
  } catch (error: any) {
    console.error('[Auth] Login error:', error);
    console.error('[Auth] Error stack:', error.stack);
    res.status(500).json({
      success: false,
      message: error.message || 'Internal server error',
    });
  }
};

export const getMe = async (req: AuthRequest, res: Response): Promise<void> => {
  const startTime = Date.now();
  // console.log(`[Auth Controller] getMe started for user ${req.user?._id}`);
  try {
    if (!req.user) {
      res.status(401).json({
        success: false,
        message: 'Not authenticated',
      });
      return;
    }

    // CSM-sourced session: identity lives in the JWT, not in local `users`.
    if (isCsmEnabled() && (req.user as any).csm) {
      const csmSession = buildCsmSessionUser(
        { id: req.user._id, email: req.user.email, name: req.user.name } as any,
        req.user.role
      );
      await attachUserRoleFields(csmSession as any);
      res.json({ success: true, data: csmSession });
      return;
    }

    // Fetch user without populate to avoid ObjectId casting issues
    let user: any = null;
    try {
      // Check if userId is a valid ObjectId
      if (mongoose.Types.ObjectId.isValid(req.user._id)) {
        user = await User.findById(req.user._id)
          .select('-password')
          .lean();
      } else {
        // userId is a string (demo data) - use native MongoDB query to bypass Mongoose casting
        const db = mongoose.connection.db;
        if (!db) throw new Error('Database connection not established');
        const usersCollection = db.collection('users');
        user = await usersCollection.findOne(
          { _id: req.user._id } as any,
          { projection: { password: 0 } }
        );
      }
    } catch (e: any) {
      // If query fails, try using native MongoDB collection
      console.warn(`[Auth Controller] Error finding user with ID: ${req.user._id}`, e.message);
      try {
        const db = mongoose.connection.db;
        if (!db) throw new Error('Database connection not established');
        const usersCollection = db.collection('users');
        user = await usersCollection.findOne(
          { _id: req.user._id } as any,
          { projection: { password: 0 } }
        );
      } catch (nativeError) {
        console.error(`[Auth Controller] Failed to find user with native query:`, nativeError);
      }
    }

    if (!user) {
      res.status(404).json({
        success: false,
        message: 'User not found',
      });
      return;
    }

    // Manually populate companyId
    if (user.companyId) {
      let company = null;
      try {
        if (mongoose.Types.ObjectId.isValid(user.companyId)) {
          company = await Company.findById(user.companyId).select('name code').lean();
        } else {
          // companyId is a string - use native MongoDB query
          const db = mongoose.connection.db;
          if (!db) throw new Error('Database connection not established');
          const companiesCollection = db.collection('companies');
          company = await companiesCollection.findOne(
            { _id: user.companyId },
            { projection: { name: 1, code: 1 } }
          );
        }
      } catch (e) {
        console.warn(`[Auth Controller] Could not find company with ID: ${user.companyId}`, e);
      }

      if (company) {
        user.companyId = {
          _id: company._id,
          name: company.name,
          code: company.code
        };
      }
    }

    // Manually populate scope (projects)
    if (user.scope && Array.isArray(user.scope) && user.scope.length > 0) {
      const populatedScope = await Promise.all(
        user.scope.map(async (projectId: any) => {
          let project = null;
          try {
            if (mongoose.Types.ObjectId.isValid(projectId)) {
              project = await Project.findById(projectId).select('name code').lean();
            } else {
              // projectId is a string - use native MongoDB query
              const db = mongoose.connection.db;
              if (!db) throw new Error('Database connection not established');
              const projectsCollection = db.collection('projects');
              project = await projectsCollection.findOne(
                { _id: projectId },
                { projection: { name: 1, code: 1 } }
              );
            }
          } catch (e) {
            console.warn(`[Auth Controller] Could not find project with ID: ${projectId}`, e);
          }
          return project || null;
        })
      );
      user.scope = populatedScope.filter(p => p !== null);
    }

    await attachUserRoleFields(user);

    // console.log(`[Auth Controller] getMe completed in ${Date.now() - startTime}ms`);
    res.json({
      success: true,
      data: user,
    });
  } catch (error: any) {
    console.error('[Auth Controller] Error in getMe:', error);
    res.status(500).json({
      success: false,
      message: error.message || 'Failed to get user info',
    });
  }
};

export const logout = async (req: AuthRequest, res: Response): Promise<void> => {
  try {
    if (req.user) {
      await AuditLog.create({
        userId: req.user._id,
        action: 'LOGIN',
        targetCollection: 'users',
        targetId: req.user._id,
        details: `User logged out: ${req.user.email}`,
        ipAddress: req.ip,
        userAgent: req.get('user-agent'),
      });
    }

    res.json({
      success: true,
      message: 'Logged out successfully',
    });
  } catch (error: any) {
    res.status(500).json({
      success: false,
      message: error.message || 'Logout failed',
    });
  }
};
