const crypto =
  require("crypto");

const bcrypt =
  require("bcryptjs");

const jwt =
  require("jsonwebtoken");

const {
  pool
} = require("../config/db");

const {
  writeAdminAuditLog
} = require(
  "../services/adminAuditService"
);

/* =========================================================
   CONFIGURATION
   ========================================================= */

const getPositiveInteger = (
  value,
  fallback
) => {
  const parsedValue =
    Number(value);

  if (
    !Number.isInteger(
      parsedValue
    ) ||
    parsedValue < 1
  ) {
    return fallback;
  }

  return parsedValue;
};

const maximumLoginAttempts =
  getPositiveInteger(
    process.env
      .ADMIN_LOGIN_MAX_ATTEMPTS,
    5
  );

const accountLockMinutes =
  getPositiveInteger(
    process.env
      .ADMIN_ACCOUNT_LOCK_MINUTES,
    30
  );

const adminTokenExpiry =
  process.env
    .ADMIN_JWT_EXPIRES_IN ||
  "2h";

/* =========================================================
   HELPERS
   ========================================================= */

const getRequestIp = (
  request
) => {
  return (
    request.ip ||
    request.socket
      ?.remoteAddress ||
    null
  );
};

const getUserAgent = (
  request
) => {
  const userAgent =
    request.get(
      "user-agent"
    );

  return userAgent
    ? userAgent.slice(
        0,
        500
      )
    : null;
};

const hashSessionToken = (
  sessionToken
) => {
  return crypto
    .createHash("sha256")
    .update(sessionToken)
    .digest("hex");
};

const mapAdminUser = (
  adminUser
) => ({
  id:
    adminUser.id,

  full_name:
    adminUser.full_name,

  username:
    adminUser.username,

  email:
    adminUser.email,

  role:
    adminUser.role,

  status:
    adminUser.status,

  last_login_at:
    adminUser.last_login_at,

  password_changed_at:
    adminUser.password_changed_at
});

const validatePasswordStrength = (
  password
) => {
  if (
    typeof password !==
      "string" ||
    password.length < 12 ||
    password.length > 128
  ) {
    return (
      "Password must contain between " +
      "12 and 128 characters."
    );
  }

  if (
    !/[a-z]/.test(password)
  ) {
    return (
      "Password must contain at least " +
      "one lowercase letter."
    );
  }

  if (
    !/[A-Z]/.test(password)
  ) {
    return (
      "Password must contain at least " +
      "one uppercase letter."
    );
  }

  if (
    !/\d/.test(password)
  ) {
    return (
      "Password must contain at least " +
      "one number."
    );
  }

  if (
    !/[^A-Za-z0-9]/.test(
      password
    )
  ) {
    return (
      "Password must contain at least " +
      "one special character."
    );
  }

  return null;
};

const createAdminSession =
  async ({
    connection,
    adminUser,
    request
  }) => {
    const jwtSecret =
      process.env
        .ADMIN_JWT_SECRET;

    if (!jwtSecret) {
      throw new Error(
        "ADMIN_JWT_SECRET is not configured."
      );
    }

    const rawSessionToken =
      crypto
        .randomBytes(32)
        .toString("hex");

    const accessToken =
      jwt.sign(
        {
          sub:
            String(
              adminUser.id
            ),

          sid:
            rawSessionToken,

          role:
            adminUser.role,

          type:
            "admin_access"
        },

        jwtSecret,

        {
          expiresIn:
            adminTokenExpiry,

          algorithm:
            "HS256",

          issuer:
            "cagd-srms",

          audience:
            "cagd-admin"
        }
      );

    const decodedToken =
      jwt.decode(
        accessToken
      );

    if (
      !decodedToken?.exp
    ) {
      throw new Error(
        "Unable to determine administrator token expiry."
      );
    }

    const expiresAt =
      new Date(
        decodedToken.exp *
          1000
      );

    const sessionTokenHash =
      hashSessionToken(
        rawSessionToken
      );

    const [insertResult] =
      await connection.query(
        `
          INSERT INTO admin_sessions (
            admin_user_id,
            session_token_hash,
            ip_address,
            user_agent,
            expires_at,
            last_used_at
          )
          VALUES (
            ?,
            ?,
            ?,
            ?,
            ?,
            NOW()
          )
        `,
        [
          adminUser.id,
          sessionTokenHash,
          getRequestIp(
            request
          ),
          getUserAgent(
            request
          ),
          expiresAt
        ]
      );

    return {
      accessToken,
      expiresAt,
      sessionId:
        insertResult.insertId
    };
  };

/* =========================================================
   ADMIN LOGIN
   POST /api/admin/auth/login
   ========================================================= */

const login = async (
  request,
  response
) => {
  let connection = null;
  let transactionStarted = false;

  try {
    const identifier =
      String(
        request.body
          ?.identifier ||
        request.body
          ?.username_or_email ||
        ""
      )
        .trim()
        .toLowerCase();

    const password =
      String(
        request.body
          ?.password ||
        ""
      );

    const validationErrors = {};

    if (!identifier) {
      validationErrors.identifier =
        "Enter your username or email address.";
    }

    if (!password) {
      validationErrors.password =
        "Enter your password.";
    }

    if (
      Object.keys(
        validationErrors
      ).length > 0
    ) {
      return response.status(422).json({
        success: false,
        message:
          "Enter your administrator login details.",
        errors:
          validationErrors
      });
    }

    connection =
      await pool.getConnection();

    await connection.beginTransaction();

    transactionStarted = true;

    const [adminRows] =
      await connection.query(
        `
          SELECT
            id,
            full_name,
            username,
            email,
            password_hash,
            role,
            status,
            failed_login_attempts,
            locked_until,
            last_login_at,
            password_changed_at
          FROM admin_users
          WHERE
            LOWER(username) = ?
            OR LOWER(email) = ?
          LIMIT 1
          FOR UPDATE
        `,
        [
          identifier,
          identifier
        ]
      );

    const adminUser =
      adminRows[0];

    if (!adminUser) {
      await writeAdminAuditLog({
        connection,

        action:
          "ADMIN_LOGIN_FAILED",

        description:
          "Administrator login failed for an unknown account.",

        metadata: {
          identifier
        },

        ipAddress:
          getRequestIp(
            request
          ),

        userAgent:
          getUserAgent(
            request
          )
      });

      await connection.commit();

      transactionStarted = false;

      return response.status(401).json({
        success: false,
        message:
          "The username/email or password is incorrect."
      });
    }

    /*
     * Automatically unlock a temporarily
     * locked account when its lock period
     * has expired.
     */

    if (
      adminUser.status ===
        "locked" &&
      adminUser.locked_until &&
      new Date(
        adminUser.locked_until
      ).getTime() <= Date.now()
    ) {
      await connection.query(
        `
          UPDATE admin_users
          SET
            status = 'active',
            failed_login_attempts = 0,
            locked_until = NULL
          WHERE id = ?
        `,
        [
          adminUser.id
        ]
      );

      adminUser.status =
        "active";

      adminUser
        .failed_login_attempts =
        0;

      adminUser.locked_until =
        null;
    }

    if (
      adminUser.status ===
      "inactive"
    ) {
      await writeAdminAuditLog({
        connection,

        adminUserId:
          adminUser.id,

        action:
          "ADMIN_LOGIN_BLOCKED",

        description:
          "Login was blocked because the administrator account is inactive.",

        ipAddress:
          getRequestIp(
            request
          ),

        userAgent:
          getUserAgent(
            request
          )
      });

      await connection.commit();

      transactionStarted = false;

      return response.status(403).json({
        success: false,
        message:
          "This administrator account is inactive."
      });
    }

    if (
      adminUser.status ===
      "locked"
    ) {
      await connection.commit();

      transactionStarted = false;

      return response.status(423).json({
        success: false,
        message:
          "This administrator account is temporarily locked. Try again later."
      });
    }

    const passwordMatches =
      await bcrypt.compare(
        password,
        adminUser.password_hash
      );

    if (!passwordMatches) {
      const nextAttemptCount =
        Number(
          adminUser
            .failed_login_attempts
        ) + 1;

      const shouldLockAccount =
        nextAttemptCount >=
        maximumLoginAttempts;

      const lockedUntil =
        shouldLockAccount
          ? new Date(
              Date.now() +
                accountLockMinutes *
                  60 *
                  1000
            )
          : null;

      await connection.query(
        `
          UPDATE admin_users
          SET
            failed_login_attempts = ?,
            status = ?,
            locked_until = ?
          WHERE id = ?
        `,
        [
          nextAttemptCount,

          shouldLockAccount
            ? "locked"
            : "active",

          lockedUntil,

          adminUser.id
        ]
      );

      await writeAdminAuditLog({
        connection,

        adminUserId:
          adminUser.id,

        action:
          shouldLockAccount
            ? "ADMIN_ACCOUNT_LOCKED"
            : "ADMIN_LOGIN_FAILED",

        description:
          shouldLockAccount
            ? "Administrator account was temporarily locked after repeated failed login attempts."
            : "Administrator login failed because an incorrect password was supplied.",

        metadata: {
          failed_login_attempts:
            nextAttemptCount
        },

        ipAddress:
          getRequestIp(
            request
          ),

        userAgent:
          getUserAgent(
            request
          )
      });

      await connection.commit();

      transactionStarted = false;

      return response
        .status(
          shouldLockAccount
            ? 423
            : 401
        )
        .json({
          success: false,

          message:
            shouldLockAccount
              ? `This administrator account has been locked for ${accountLockMinutes} minutes.`
              : "The username/email or password is incorrect."
        });
    }

    await connection.query(
      `
        UPDATE admin_users
        SET
          failed_login_attempts = 0,
          locked_until = NULL,
          status = 'active',
          last_login_at = NOW()
        WHERE id = ?
      `,
      [
        adminUser.id
      ]
    );

    const session =
      await createAdminSession({
        connection,
        adminUser,
        request
      });

    await writeAdminAuditLog({
      connection,

      adminUserId:
        adminUser.id,

      action:
        "ADMIN_LOGIN_SUCCESS",

      entityType:
        "admin_session",

      entityId:
        session.sessionId,

      description:
        "Administrator logged into the HR review portal.",

      ipAddress:
        getRequestIp(
          request
        ),

      userAgent:
        getUserAgent(
          request
        )
    });

    await connection.commit();

    transactionStarted = false;

    adminUser.last_login_at =
      new Date();

    return response.status(200).json({
      success: true,

      message:
        "Administrator login successful.",

      data: {
        access_token:
          session.accessToken,

        token_type:
          "Bearer",

        expires_at:
          session.expiresAt,

        admin:
          mapAdminUser(
            adminUser
          )
      }
    });
  } catch (error) {
    if (
      connection &&
      transactionStarted
    ) {
      try {
        await connection.rollback();
      } catch (rollbackError) {
        console.error(
          "Admin login rollback error:",
          rollbackError
        );
      }
    }

    console.error(
      "Admin login error:",
      error
    );

    return response.status(500).json({
      success: false,
      message:
        "Unable to complete the administrator login."
    });
  } finally {
    if (connection) {
      connection.release();
    }
  }
};

/* =========================================================
   GET CURRENT ADMIN
   GET /api/admin/auth/me
   ========================================================= */

const getCurrentAdmin = async (
  request,
  response
) => {
  return response.status(200).json({
    success: true,

    data: {
      admin:
        request.admin,

      session: {
        expires_at:
          request.adminSession
            ?.expires_at ||
          null
      }
    }
  });
};

/* =========================================================
   ADMIN LOGOUT
   POST /api/admin/auth/logout
   ========================================================= */

const logout = async (
  request,
  response
) => {
  try {
    await pool.query(
      `
        UPDATE admin_sessions
        SET
          revoked_at = NOW(),
          revoked_reason =
            'user_logout'
        WHERE
          id = ?
          AND revoked_at IS NULL
      `,
      [
        request.adminSession.id
      ]
    );

    await writeAdminAuditLog({
      adminUserId:
        request.admin.id,

      action:
        "ADMIN_LOGOUT",

      entityType:
        "admin_session",

      entityId:
        request.adminSession.id,

      description:
        "Administrator logged out of the HR review portal.",

      ipAddress:
        getRequestIp(
          request
        ),

      userAgent:
        getUserAgent(
          request
        )
    });

    return response.status(200).json({
      success: true,
      message:
        "You have been logged out successfully."
    });
  } catch (error) {
    console.error(
      "Admin logout error:",
      error
    );

    return response.status(500).json({
      success: false,
      message:
        "Unable to complete the administrator logout."
    });
  }
};

/* =========================================================
   CHANGE ADMIN PASSWORD
   PATCH /api/admin/auth/change-password
   ========================================================= */

const changePassword = async (
  request,
  response
) => {
  let connection = null;
  let transactionStarted = false;

  try {
    const currentPassword =
      String(
        request.body
          ?.current_password ||
        ""
      );

    const newPassword =
      String(
        request.body
          ?.new_password ||
        ""
      );

    const passwordConfirmation =
      String(
        request.body
          ?.new_password_confirmation ||
        request.body
          ?.confirm_password ||
        ""
      );

    const validationErrors = {};

    if (!currentPassword) {
      validationErrors
        .current_password =
        "Enter your current password.";
    }

    const passwordError =
      validatePasswordStrength(
        newPassword
      );

    if (passwordError) {
      validationErrors
        .new_password =
        passwordError;
    }

    if (
      newPassword !==
      passwordConfirmation
    ) {
      validationErrors
        .new_password_confirmation =
        "The new password confirmation does not match.";
    }

    if (
      Object.keys(
        validationErrors
      ).length > 0
    ) {
      return response.status(422).json({
        success: false,
        message:
          "Some password details require correction.",
        errors:
          validationErrors
      });
    }

    connection =
      await pool.getConnection();

    await connection.beginTransaction();

    transactionStarted = true;

    const [adminRows] =
      await connection.query(
        `
          SELECT
            id,
            password_hash
          FROM admin_users
          WHERE id = ?
          LIMIT 1
          FOR UPDATE
        `,
        [
          request.admin.id
        ]
      );

    const adminUser =
      adminRows[0];

    if (!adminUser) {
      await connection.rollback();

      transactionStarted = false;

      return response.status(404).json({
        success: false,
        message:
          "The administrator account could not be found."
      });
    }

    const currentPasswordMatches =
      await bcrypt.compare(
        currentPassword,
        adminUser.password_hash
      );

    if (
      !currentPasswordMatches
    ) {
      await connection.rollback();

      transactionStarted = false;

      return response.status(422).json({
        success: false,
        message:
          "The current password is incorrect.",
        errors: {
          current_password:
            "Enter your correct current password."
        }
      });
    }

    const sameAsCurrentPassword =
      await bcrypt.compare(
        newPassword,
        adminUser.password_hash
      );

    if (
      sameAsCurrentPassword
    ) {
      await connection.rollback();

      transactionStarted = false;

      return response.status(422).json({
        success: false,
        message:
          "Choose a password different from your current password.",
        errors: {
          new_password:
            "The new password cannot be the same as the current password."
        }
      });
    }

    const passwordHash =
      await bcrypt.hash(
        newPassword,
        12
      );

    await connection.query(
      `
        UPDATE admin_users
        SET
          password_hash = ?,
          password_changed_at =
            NOW(),
          failed_login_attempts = 0,
          locked_until = NULL,
          status = 'active'
        WHERE id = ?
      `,
      [
        passwordHash,
        request.admin.id
      ]
    );

    /*
     * Revoke every active session, including
     * the current one.
     */

    await connection.query(
      `
        UPDATE admin_sessions
        SET
          revoked_at = NOW(),
          revoked_reason =
            'password_changed'
        WHERE
          admin_user_id = ?
          AND revoked_at IS NULL
      `,
      [
        request.admin.id
      ]
    );

    await writeAdminAuditLog({
      connection,

      adminUserId:
        request.admin.id,

      action:
        "ADMIN_PASSWORD_CHANGED",

      entityType:
        "admin_user",

      entityId:
        request.admin.id,

      description:
        "Administrator changed their password. All active sessions were revoked.",

      ipAddress:
        getRequestIp(
          request
        ),

      userAgent:
        getUserAgent(
          request
        )
    });

    await connection.commit();

    transactionStarted = false;

    return response.status(200).json({
      success: true,

      message:
        "Your password was changed successfully. Sign in again with the new password.",

      data: {
        requires_reauthentication:
          true
      }
    });
  } catch (error) {
    if (
      connection &&
      transactionStarted
    ) {
      try {
        await connection.rollback();
      } catch (rollbackError) {
        console.error(
          "Change password rollback error:",
          rollbackError
        );
      }
    }

    console.error(
      "Change admin password error:",
      error
    );

    return response.status(500).json({
      success: false,
      message:
        "Unable to change the administrator password."
    });
  } finally {
    if (connection) {
      connection.release();
    }
  }
};

module.exports = {
  login,
  getCurrentAdmin,
  logout,
  changePassword
};