import { EmailAddress } from "@/core/common/domain";
import { Result } from "@repo/rdx-utils";
import {
  AuthenticatedUser,
  type IJWTPayload,
  type LoginData,
  type RegisterData,
  TabContext,
  Token,
} from "..";

import { UniqueID } from "@/core/common/domain";
import { IAuthenticatedUserRepository, JWTPayload } from "..";
import { JwtHelper } from "../../../../../../../modules/auth/src/api/lib/passport/jwt.helper";
import { ITabContextRepository } from "../repositories/tab-context-repository.interface";
import { IAuthService } from "./auth-service.interface";

const ACCESS_EXPIRATION = process.env.JWT_ACCESS_EXPIRATION || "1h";
const REFRESH_EXPIRATION = process.env.JWT_REFRESH_EXPIRATION || "7d";

export class AuthService implements IAuthService {
  constructor(
    private readonly authUserRepo: IAuthenticatedUserRepository,
    private readonly tabContextRepo: ITabContextRepository
  ) {}

  generateAccessToken(payload: IJWTPayload): Result<Token, Error> {
    const data = payload.toPersistenceData();
    return Token.create(JwtHelper.generateToken(data, ACCESS_EXPIRATION));
  }

  generateRefreshToken(payload: IJWTPayload): Result<Token, Error> {
    const data = payload.toPersistenceData();
    return Token.create(JwtHelper.generateToken(data, REFRESH_EXPIRATION));
  }

  verifyRefreshToken(token: Token): IJWTPayload {
    return JwtHelper.verifyToken(token.toString());
  }

  /**
   *
   * Registra un nuevo usuario en la base de datos bajo transacción.
   */
  async registerUser(
    registerData: RegisterData,
    transaction?: any
  ): Promise<Result<AuthenticatedUser, Error>> {
    const { username, email, hashPassword } = registerData;

    // Verificar si el usuario ya existe
    const userExists = await this.authUserRepo.userExists(username, email, transaction);
    if (userExists.isSuccess && userExists.data) {
      return Result.fail(new Error("Email is already registered"));
    }

    const newUserId = UniqueID.generateNewID().data;

    const userOrError = AuthenticatedUser.create(
      {
        username,
        email,
        hashPassword,
        roles: ["USER"],
      },
      newUserId
    );

    if (userOrError.isFailure) {
      return Result.fail(userOrError.error);
    }

    const createdResult = await this.authUserRepo.createUser(userOrError.data, transaction);

    if (createdResult.isFailure) {
      return Result.fail(createdResult.error);
    }

    return Result.ok(userOrError.data);
  }

  /**
   *
   * Autentica a un usuario validando su email y contraseña.
   */
  async loginUser(
    loginData: LoginData,
    transaction?: any
  ): Promise<
    Result<
      {
        user: AuthenticatedUser;
        tabContext: TabContext;
        tokens: {
          accessToken: Token;
          refreshToken: Token;
        };
      },
      Error
    >
  > {
    let result: any;
    const { email, plainPassword, tabId } = loginData;

    // 🔹 Verificar si el usuario existe en la base de datos
    result = await this.authUserRepo.getUserByEmail(email, transaction);
    if (result.isFailure) {
      return Result.fail(new Error("Invalid email or password"));
    }

    const user = result.data;

    // 🔹 Verificar que la contraseña sea correcta
    const isValidPassword = await user.verifyPassword(plainPassword);
    if (!isValidPassword) {
      return Result.fail(new Error("Invalid email or password"));
    }

    // Registrar o actualizar el contexto de ese tab ID
    const contextOrError = TabContext.create({
      userId: user.id,
      tabId: tabId,
    });

    // 🔹 Generar Access Token y Refresh Token
    const payloadOrError = JWTPayload.create({
      userId: user.id,
      email: email,
      tabId: tabId,
      //roles: ["USER"],
    });

    result = Result.combine([contextOrError, payloadOrError]);
    if (result.isFailure) {
      return Result.fail(new Error("Error on login"));
    }

    const tabContext = contextOrError.data;
    await this.tabContextRepo.registerContextByTabId(tabContext, transaction);

    const accessTokenOrError = this.generateAccessToken(payloadOrError.data);
    const refreshTokenOrError = this.generateRefreshToken(payloadOrError.data);

    result = Result.combine([accessTokenOrError, refreshTokenOrError]);

    if (result.isFailure) {
      return Result.fail(result.error);
    }

    return Result.ok({
      user,
      tabContext,
      tokens: {
        accessToken: accessTokenOrError.data,
        refreshToken: refreshTokenOrError.data,
      },
    });
  }

  /**
   *
   * Autentica a un usuario validando su email y contraseña.
   */
  async logoutUser(
    params: { email: EmailAddress; tabId: UniqueID },
    transaction?: any
  ): Promise<Result<void, Error>> {
    const { email, tabId } = params;

    // 🔹 Verificar si el usuario existe en la base de datos
    const userResult = await this.authUserRepo.getUserByEmail(email, transaction);
    if (userResult.isFailure) {
      return Result.fail(new Error("Invalid email or password"));
    }

    const user = userResult.data;

    const contextOrError = TabContext.create({
      userId: user.id,
      tabId: tabId,
    });

    if (contextOrError.isFailure) {
      return Result.fail(new Error("Error creating user context"));
    }

    // Desregistrar el contexto de ese tab ID
    await this.tabContextRepo.unregisterContextByTabId(contextOrError.data, transaction);

    return Result.ok();
  }

  async getUserByEmail(
    email: EmailAddress,
    transaction?: any
  ): Promise<Result<AuthenticatedUser, Error>> {
    const userResult = await this.authUserRepo.getUserByEmail(email, transaction);

    if (userResult.isFailure || !userResult.data) {
      return Result.fail(new Error("Invalid email or password"));
    }

    return Result.ok(userResult.data);
  }
}
