import {
  Injectable,
  Inject,
  BadRequestException,
  NotFoundException,
} from "@nestjs/common";
import { Logger } from "nestjs-pino";
import type { User as PrismaUser } from "@prisma/client";
import type { User } from "@customer-portal/domain/customer";
import type { Address } from "@customer-portal/domain/customer";
import type { DashboardSummary } from "@customer-portal/domain/dashboard";
import type { UpdateCustomerProfileRequest } from "@customer-portal/domain/auth";
import { UserAuthRepository } from "../infra/user-auth.repository";
import { UserProfileService } from "../infra/user-profile.service";
import { getErrorMessage } from "@bff/core/utils/error.util";
import { mapPrismaUserToDomain } from "@bff/infra/mappers";

type AuthUpdateData = Partial<
  Pick<PrismaUser, "passwordHash" | "failedLoginAttempts" | "lastLoginAt" | "lockedUntil">
>;

@Injectable()
export class UsersFacade {
  constructor(
    private readonly authRepository: UserAuthRepository,
    private readonly profileService: UserProfileService,
    @Inject(Logger) private readonly logger: Logger
  ) {}

  async findByEmail(email: string): Promise<User | null> {
    const user = await this.authRepository.findByEmail(email);
    if (!user) {
      return null;
    }

    try {
      return await this.profileService.getProfile(user.id);
    } catch (error) {
      this.logger.error("Failed to build profile by email", {
        email,
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  async findByEmailInternal(email: string): Promise<PrismaUser | null> {
    return this.authRepository.findByEmail(email);
  }

  async findById(id: string): Promise<User | null> {
    return this.profileService.findById(id);
  }

  async findByIdInternal(id: string): Promise<PrismaUser | null> {
    return this.authRepository.findById(id);
  }

  async getProfile(userId: string): Promise<User> {
    return this.profileService.getProfile(userId);
  }

  async getAddress(userId: string): Promise<Address | null> {
    return this.profileService.getAddress(userId);
  }

  async updateAddress(userId: string, update: Partial<Address>): Promise<Address> {
    return this.profileService.updateAddress(userId, update);
  }

  async updateProfile(userId: string, update: UpdateCustomerProfileRequest): Promise<User> {
    return this.profileService.updateProfile(userId, update);
  }

  async getUserSummary(userId: string): Promise<DashboardSummary> {
    return this.profileService.getUserSummary(userId);
  }

  async create(
    userData: Partial<PrismaUser>,
    options?: { includeProfile?: boolean }
  ): Promise<User | ReturnType<typeof mapPrismaUserToDomain>> {
    try {
      const createdUser = await this.authRepository.create(userData);
      const includeProfile = options?.includeProfile ?? true;
      if (includeProfile) {
        return this.profileService.getProfile(createdUser.id);
      }
      return mapPrismaUserToDomain(createdUser);
    } catch (error) {
      this.logger.error("Failed to create user", {
        error: getErrorMessage(error),
      });
      throw error;
    }
  }

  async update(id: string, data: AuthUpdateData): Promise<User> {
    const sanitized = this.sanitizeAuthUpdate(data);

    try {
      await this.authRepository.updateAuthState(id, sanitized);
      return await this.profileService.getProfile(id);
    } catch (error) {
      const message = getErrorMessage(error);
      // If the profile lookup fails because the WHMCS mapping isn't created yet (e.g., during
      // initial account setup), fall back to returning the auth user to avoid bubbling a system error.
      if (error instanceof NotFoundException && /whmcs client mapping not found/i.test(message)) {
        this.logger.warn("Profile not available after auth state update; returning auth user only", {
          userId: id,
        });
        const authUser = await this.authRepository.findById(id);
        if (authUser) {
          return mapPrismaUserToDomain(authUser);
        }
      }

      this.logger.error("Failed to update user auth state", {
        userId: id,
        error: message,
      });
      throw error;
    }
  }

  private sanitizeAuthUpdate(data: AuthUpdateData): AuthUpdateData {
    if (!data) {
      throw new BadRequestException("Update payload is required");
    }

    const sanitized: AuthUpdateData = {};

    if (data.passwordHash !== undefined) {
      sanitized.passwordHash = data.passwordHash;
    }
    if (data.failedLoginAttempts !== undefined) {
      sanitized.failedLoginAttempts = data.failedLoginAttempts;
    }
    if (data.lastLoginAt !== undefined) {
      sanitized.lastLoginAt = data.lastLoginAt;
    }
    if (data.lockedUntil !== undefined) {
      sanitized.lockedUntil = data.lockedUntil;
    }

    return sanitized;
  }
}
