import { BadRequestException, ForbiddenException, Inject, Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { randomUUID } from 'crypto';
import { mkdir, readFile, writeFile } from 'fs/promises';
import { existsSync, readFileSync } from 'fs';
import { join, resolve } from 'path';
import { UsersService } from '../../core/db/users/users.service';
import { Users } from '../../core/db/users/users.entity';
import { UserProfiles } from '../../core/db/user_profiles/user_profiles.entity';
import { CreateUserProfileDto, UpdateUserProfileDto } from '../../core/db/user_profiles/user_profiles.dto';
import { PlayerAssets } from '../../core/db/player_assets/player_assets.entity';
import { EmailService } from '../../core/utils/email/email.service';

interface IUserProfilesService {
  create(createUserProfileDto: CreateUserProfileDto): Promise<UserProfiles>;
  findByUserId(userId: bigint): Promise<UserProfiles | null>;
  update(id: bigint, updateData: UpdateUserProfileDto): Promise<UserProfiles>;
}

interface IPlayerAssetsService {
  grantAsset(userId: bigint, assetType: 'skin' | 'room_decor', assetId: string, source?: string, metadata?: Record<string, unknown>): Promise<PlayerAssets>;
  hasAsset(userId: bigint, assetType: 'skin' | 'room_decor', assetId: string): Promise<boolean>;
  hasAssetFromSource(userId: bigint, assetType: 'skin' | 'room_decor', source: string): Promise<boolean>;
  listAssetIds(userId: bigint, assetType: 'skin' | 'room_decor'): Promise<string[]>;
}

interface IUserWalletsService {
  ensureWallet(userId: bigint): Promise<unknown>;
}

export interface AccountProfilePayload {
  user: {
    id: string;
    username: string;
    nickname: string;
    email?: string;
    phone?: string;
    avatar_url?: string;
    avatar_base64?: string;
    role: number;
    created_at: Date;
  };
  profile: {
    user_id: string;
    skin_id: string;
    avatar_id: string;
    avatar_url?: string;
    avatar_base64?: string;
    current_map: string;
    pos_x: number;
    pos_y: number;
    status: number;
    owned_skin_ids: string[];
    owned_skins: AccountSkinAsset[];
    settings: AccountSettings;
  };
}

export type RoomVisitPolicy = 'friends' | 'public' | 'closed';
export type AccountSettings = Record<string, boolean | number | RoomVisitPolicy>;

export interface UpdateAccountProfileRequest {
  skin_id?: string;
  avatar_url?: string;
  avatar_image_base64?: string;
  avatar_mime_type?: string;
  skin_image_base64?: string;
  skin_mime_type?: string;
  skin_name?: string;
  settings?: Record<string, unknown>;
}

const FALLBACK_SKIN_ID = 'classic_whale';
const PENDING_INITIAL_SKIN_ID = 'pending_initial_skin';
const INITIAL_SKIN_IDS = new Set([
  'classic_whale',
  'human_whale_directional_v2_8x4',
  'girl_sailor_turnaround_v2_8x4',
]);
const GENERATED_ASSETS_DIR = 'generated/account-assets';
const PUBLIC_ASSETS_PREFIX = '/assets/account';
const AVATAR_SIZE = 256;
const CUSTOM_SKIN_HFRAMES = 8;
const CUSTOM_SKIN_VFRAMES = 4;
const REGISTRATION_GENERATED_SKIN_SOURCE = 'generated_registration';
const PROFILE_SETTINGS_TAG_KEY = 'whaletown_settings';
const REGISTRATION_SKIN_GENERATION_AVAILABLE_TAG_KEY = 'registration_skin_generation_available';
const WELCOME_EMAIL_SENT_TAG_KEY = 'welcome_email_sent';
const DEFAULT_ACCOUNT_SETTINGS: AccountSettings = {
  master_volume: 0.80,
  music_volume: 0.60,
  effects_volume: 0.90,
  ui_scale: 1.00,
  fullscreen: false,
  show_interaction_hints: true,
  show_interaction_points: false,
  show_name_always: false,
  show_chat_bubbles: true,
  world_notifications: true,
  private_notifications: true,
  friend_request_notifications: true,
  allow_nearby_private: true,
  allow_nearby_friend_requests: true,
  allow_nearby_profile: true,
  room_visit_policy: 'friends',
  mute_ui_sfx: false,
};
const ACCOUNT_SETTING_NUMBER_KEYS = new Set(['master_volume', 'music_volume', 'effects_volume', 'ui_scale']);
const ACCOUNT_SETTING_BOOLEAN_KEYS = new Set([
  'fullscreen',
  'show_interaction_hints',
  'show_interaction_points',
  'show_name_always',
  'show_chat_bubbles',
  'world_notifications',
  'private_notifications',
  'friend_request_notifications',
  'allow_nearby_private',
  'allow_nearby_friend_requests',
  'allow_nearby_profile',
  'mute_ui_sfx',
]);
const ROOM_VISIT_POLICIES = new Set<RoomVisitPolicy>(['friends', 'public', 'closed']);

export interface AccountSkinAsset {
  id: string;
  name: string;
  texture_url?: string;
  texture_base64?: string;
  mime_type?: string;
  hframes: number;
  vframes: number;
  source: string;
}

@Injectable()
export class AccountProfileService {
  private readonly logger = new Logger(AccountProfileService.name);

  constructor(
    @Inject('UsersService') private readonly usersService: UsersService,
    @Inject('IUserProfilesService') private readonly userProfilesService: IUserProfilesService,
    @Inject('IPlayerAssetsService') private readonly playerAssetsService: IPlayerAssetsService,
    @Inject('IUserWalletsService') private readonly userWalletsService: IUserWalletsService,
    private readonly configService: ConfigService,
    private readonly emailService: EmailService,
  ) {}

  async getAccountProfile(userId: bigint): Promise<AccountProfilePayload> {
    const user = await this.usersService.findOne(userId);
    const profile = await this.ensureProfile(userId);
    return this.formatAccountProfile(user, profile, await this.getOwnedSkinIds(userId));
  }

  async updateAccountProfile(userId: bigint, update: UpdateAccountProfileRequest): Promise<AccountProfilePayload> {
    const user = await this.usersService.findOne(userId);
    let profile = await this.ensureProfile(userId);
    const isInitialCharacterCreation = this.isInitialCharacterPending(profile);

    let normalizedSkinId = this.normalizeSkinId(update.skin_id);
    if (update.skin_image_base64) {
      const skinAsset = await this.saveCustomSkinAsset(
        userId,
        update.skin_image_base64,
        update.skin_mime_type,
        update.skin_name,
      );
      normalizedSkinId = skinAsset.skinId;
      await this.playerAssetsService.grantAsset(userId, 'skin', normalizedSkinId, 'custom_upload');
    }

    if (normalizedSkinId) {
      await this.ensureSkinCanBeSelected(userId, normalizedSkinId);
      profile = await this.userProfilesService.update(profile.id, {
        skin_id: normalizedSkinId,
      });
      if (isInitialCharacterCreation) {
        profile = await this.sendWelcomeEmailAfterInitialCharacterCreation(user, profile);
      }
    }

    if (update.settings && typeof update.settings === 'object' && !Array.isArray(update.settings)) {
      profile = await this.userProfilesService.update(profile.id, {
        tags: this.mergeProfileTagsWithSettings(profile, update.settings),
      });
    }

    let avatarUrl = update.avatar_url;
    if (update.avatar_image_base64) {
      avatarUrl = await this.saveAvatarAsset(userId, update.avatar_image_base64, update.avatar_mime_type);
    }
    const normalizedAvatarUrl = this.normalizeAvatarUrl(avatarUrl);
    const avatarUrlWasProvided = typeof avatarUrl === 'string';
    const updatedUser = avatarUrlWasProvided
      ? await this.usersService.update(userId, { avatar_url: normalizedAvatarUrl || null } as any)
      : user;

    return this.formatAccountProfile(updatedUser, profile, await this.getOwnedSkinIds(userId));
  }

  async sendWelcomeEmailAfterInitialCharacterCreation(user: Users, profile: UserProfiles): Promise<UserProfiles> {
    if (!this.isInitialCharacterCreated(profile)) {
      return profile;
    }

    const tags = this.getProfileTags(profile);
    if (tags[WELCOME_EMAIL_SENT_TAG_KEY] === true) {
      return profile;
    }

    // Record the event before delivery so subsequent appearance saves do not repeat the welcome email.
    tags[WELCOME_EMAIL_SENT_TAG_KEY] = true;
    const updatedProfile = await this.userProfilesService.update(profile.id, { tags });

    if (!user.email) {
      this.logger.warn('初始角色已创建，但账号未绑定邮箱，跳过欢迎邮件', {
        userId: user.id.toString(),
      });
      return updatedProfile;
    }

    try {
      await this.emailService.sendWelcomeEmail(user.email, user.nickname);
    } catch (error) {
      this.logger.warn('初始角色创建后的欢迎邮件发送失败', {
        userId: user.id.toString(),
        error: error instanceof Error ? error.message : String(error),
      });
    }
    return updatedProfile;
  }

  async ensureProfile(userId: bigint, initialSkinId?: string): Promise<UserProfiles> {
    const existing = await this.userProfilesService.findByUserId(userId);
    if (existing) {
      await this.userWalletsService.ensureWallet(userId);
      return await this.ensureProfileSkinIsOwned(userId, existing);
    }

    const skinId = this.resolveInitialSkinId(initialSkinId);
    if (skinId !== PENDING_INITIAL_SKIN_ID) {
      await this.grantInitialSkins(userId, skinId);
    }
    await this.userWalletsService.ensureWallet(userId);
    this.logger.log('创建账号初始用户档案', {
      userId: userId.toString(),
      skinId,
    });

    return await this.userProfilesService.create({
      user_id: userId,
      skin_id: skinId,
      tags: {
        [REGISTRATION_SKIN_GENERATION_AVAILABLE_TAG_KEY]: true,
      },
      current_map: 'whale_port',
      pos_x: 0,
      pos_y: 0,
      status: 0,
    });
  }

  async formatAccountProfileAsync(user: Users, profile: UserProfiles): Promise<AccountProfilePayload> {
    return this.formatAccountProfile(user, profile, await this.getOwnedSkinIds(profile.user_id));
  }

  formatAccountProfile(user: Users, profile: UserProfiles, ownedSkinIds: string[] = []): AccountProfilePayload {
    const skinId = profile.skin_id || '';
    const normalizedOwnedSkinIds = this.mergeUniqueSkinIds(ownedSkinIds);
    const avatarUrl = user.avatar_url || '';
    const avatarBase64 = this.readAccountAvatarBase64(user.id, avatarUrl);
    return {
      user: {
        id: user.id.toString(),
        username: user.username,
        nickname: user.nickname,
        email: user.email,
        phone: user.phone,
        avatar_url: avatarUrl,
        avatar_base64: avatarBase64,
        role: user.role,
        created_at: user.created_at,
      },
      profile: {
        user_id: profile.user_id.toString(),
        skin_id: skinId,
        avatar_id: avatarUrl ? 'custom' : 'default',
        avatar_url: avatarUrl,
        avatar_base64: avatarBase64,
        current_map: profile.current_map,
        pos_x: profile.pos_x,
        pos_y: profile.pos_y,
        status: profile.status,
        owned_skin_ids: normalizedOwnedSkinIds,
        owned_skins: this.getOwnedSkinAssets(user.id, normalizedOwnedSkinIds),
        settings: this.getAccountSettings(profile),
      },
    };
  }

  async saveGeneratedSkinForUser(
    userId: bigint,
    sourcePngPath: string,
    displayName = '生成角色',
    source = 'generated',
  ): Promise<AccountSkinAsset & { skinId: string }> {
    if (!sourcePngPath || !existsSync(sourcePngPath)) {
      throw new BadRequestException('生成皮肤文件不存在');
    }

    const image = await readFile(sourcePngPath);
    this.validatePngImage(image, '角色皮肤');
    const skinId = this.createCustomSkinId('generated', displayName);
    const targetPath = this.getAccountSkinPath(userId, skinId);
    await mkdir(resolve(targetPath, '..'), { recursive: true });
    await writeFile(targetPath, image);
    await this.writeSkinMetadata(userId, skinId, displayName, source);
    await this.playerAssetsService.grantAsset(userId, 'skin', skinId, source);
    await this.updateAccountProfile(userId, { skin_id: skinId });
    return {
      ...await this.buildSkinAsset(userId, skinId),
      skinId,
    };
  }

  async hasRegistrationGeneratedSkin(userId: bigint): Promise<boolean> {
    if (this.playerAssetsService.hasAssetFromSource) {
      return this.playerAssetsService.hasAssetFromSource(userId, 'skin', REGISTRATION_GENERATED_SKIN_SOURCE);
    }
    const skinIds = await this.playerAssetsService.listAssetIds(userId, 'skin');
    return skinIds.some((skinId) => skinId.startsWith('generated_'));
  }

  async canUseRegistrationSkinGeneration(userId: bigint): Promise<boolean> {
    const profile = await this.ensureProfile(userId);
    const tags = this.getProfileTags(profile);
    return tags[REGISTRATION_SKIN_GENERATION_AVAILABLE_TAG_KEY] === true;
  }

  async consumeRegistrationSkinGeneration(userId: bigint): Promise<void> {
    const profile = await this.ensureProfile(userId);
    const tags = this.getProfileTags(profile);
    tags[REGISTRATION_SKIN_GENERATION_AVAILABLE_TAG_KEY] = false;
    await this.userProfilesService.update(profile.id, { tags });
  }

  private async grantInitialSkins(userId: bigint, selectedSkinId: string): Promise<void> {
    if (this.isInitialSkinId(selectedSkinId)) {
      await this.playerAssetsService.grantAsset(userId, 'skin', selectedSkinId, 'registration');
    }
  }

  private async ensureProfileSkinIsOwned(userId: bigint, profile: UserProfiles): Promise<UserProfiles> {
    const selectedSkinId = this.normalizeSkinId(profile.skin_id || '');
    if (!selectedSkinId || selectedSkinId === PENDING_INITIAL_SKIN_ID) {
      return profile;
    }
    if (await this.playerAssetsService.hasAsset(userId, 'skin', selectedSkinId)) {
      return profile;
    }
    if (this.isInitialSkinId(selectedSkinId)) {
      await this.playerAssetsService.grantAsset(userId, 'skin', selectedSkinId, 'registration');
      return profile;
    }
    return profile;
  }

  private async ensureSkinCanBeSelected(userId: bigint, skinId: string): Promise<void> {
    if (this.isInitialSkinId(skinId)) {
      const profile = await this.userProfilesService.findByUserId(userId);
      const currentSkinId = this.normalizeSkinId(profile?.skin_id || '');
      const ownedSkinIds = await this.playerAssetsService.listAssetIds(userId, 'skin');
      if ((currentSkinId === PENDING_INITIAL_SKIN_ID || ownedSkinIds.length === 0) && !(await this.playerAssetsService.hasAsset(userId, 'skin', skinId))) {
        await this.playerAssetsService.grantAsset(userId, 'skin', skinId, 'registration');
        return;
      }
    }
    if (!(await this.playerAssetsService.hasAsset(userId, 'skin', skinId))) {
      throw new ForbiddenException('尚未拥有该皮肤，请先在商城购买');
    }
  }

  private async getOwnedSkinIds(userId: bigint): Promise<string[]> {
    return this.mergeUniqueSkinIds(await this.playerAssetsService.listAssetIds(userId, 'skin'));
  }

  private mergeUniqueSkinIds(skinIds: string[]): string[] {
    const seen = new Set<string>();
    const result: string[] = [];
    for (const skinId of skinIds) {
      const normalized = this.normalizeSkinId(skinId);
      if (normalized && !seen.has(normalized)) {
        seen.add(normalized);
        result.push(normalized);
      }
    }
    return result;
  }

  private normalizeSkinId(skinId?: string): string {
    const normalized = (skinId || '').trim();
    if (!normalized) {
      return '';
    }
    if (!/^[A-Za-z0-9_:-]{1,100}$/.test(normalized)) {
      throw new BadRequestException('皮肤ID格式不正确');
    }
    return normalized;
  }

  private resolveInitialSkinId(skinId?: string): string {
    const normalized = this.normalizeSkinId(skinId);
    if (!normalized) {
      return PENDING_INITIAL_SKIN_ID;
    }
    return this.isInitialSkinId(normalized) ? normalized : PENDING_INITIAL_SKIN_ID;
  }

  private isInitialSkinId(skinId: string): boolean {
    return INITIAL_SKIN_IDS.has((skinId || '').trim());
  }

  private isInitialCharacterPending(profile: UserProfiles): boolean {
    const skinId = this.normalizeSkinId(profile.skin_id || '');
    return !skinId || skinId === PENDING_INITIAL_SKIN_ID;
  }

  private isInitialCharacterCreated(profile: UserProfiles): boolean {
    return !this.isInitialCharacterPending(profile);
  }

  private normalizeAvatarUrl(avatarUrl?: string): string {
    const normalized = (avatarUrl || '').trim();
    if (!normalized) {
      return '';
    }
    if (normalized.length > 255) {
      throw new BadRequestException('头像URL长度不能超过255字符');
    }
    return normalized;
  }

  private async saveAvatarAsset(userId: bigint, base64: string, mimeType?: string): Promise<string> {
    const buffer = this.decodeBase64Image(base64, mimeType, ['image/png', 'image/jpeg', 'image/webp'], '头像');
    if (buffer.length > 3 * 1024 * 1024) {
      throw new BadRequestException('头像图片不能超过3MB');
    }
    const extension = this.extensionForMime(mimeType || 'image/png');
    const relativePath = `${userId.toString()}/avatar_${Date.now()}.${extension}`;
    const targetPath = join(this.getAccountAssetRoot(), relativePath);
    await mkdir(resolve(targetPath, '..'), { recursive: true });
    await writeFile(targetPath, buffer);
    return `${PUBLIC_ASSETS_PREFIX}/${relativePath}`;
  }

  private async saveCustomSkinAsset(
    userId: bigint,
    base64: string,
    mimeType?: string,
    skinName?: string,
  ): Promise<{ skinId: string; textureUrl: string }> {
    const buffer = this.decodeBase64Image(base64, mimeType, ['image/png'], '角色皮肤');
    if (buffer.length > 8 * 1024 * 1024) {
      throw new BadRequestException('角色皮肤PNG不能超过8MB');
    }
    this.validatePngImage(buffer, '角色皮肤');
    const skinId = this.createCustomSkinId('custom', skinName || '自定义角色');
    const targetPath = this.getAccountSkinPath(userId, skinId);
    await mkdir(resolve(targetPath, '..'), { recursive: true });
    await writeFile(targetPath, buffer);
    await this.writeSkinMetadata(userId, skinId, skinName || '自定义角色', 'custom_upload');
    return {
      skinId,
      textureUrl: this.getAccountSkinUrl(userId, skinId),
    };
  }

  private getOwnedSkinAssets(userId: bigint, skinIds: string[]): AccountSkinAsset[] {
    const assets: AccountSkinAsset[] = [];
    for (const skinId of skinIds) {
      if (!this.isCustomAssetSkinId(skinId)) {
        continue;
      }
      const filePath = this.getAccountSkinPath(userId, skinId);
      if (!existsSync(filePath)) {
        continue;
      }
      assets.push({
        id: skinId,
        name: this.skinNameFromId(skinId),
        texture_url: this.getAccountSkinUrl(userId, skinId),
        texture_base64: readFileSync(filePath).toString('base64'),
        hframes: CUSTOM_SKIN_HFRAMES,
        vframes: CUSTOM_SKIN_VFRAMES,
        mime_type: 'image/png',
        source: skinId.startsWith('generated_') ? 'generated' : 'custom_upload',
      });
    }
    return assets;
  }

  private async buildSkinAsset(userId: bigint, skinId: string): Promise<AccountSkinAsset> {
    const texturePath = this.getAccountSkinPath(userId, skinId);
    let textureBase64 = '';
    if (existsSync(texturePath)) {
      textureBase64 = (await readFile(texturePath)).toString('base64');
    }
    return {
      id: skinId,
      name: this.skinNameFromId(skinId),
      texture_url: this.getAccountSkinUrl(userId, skinId),
      texture_base64: textureBase64,
      mime_type: 'image/png',
      hframes: CUSTOM_SKIN_HFRAMES,
      vframes: CUSTOM_SKIN_VFRAMES,
      source: skinId.startsWith('generated_') ? 'generated' : 'custom_upload',
    };
  }

  private decodeBase64Image(base64: string, mimeType: string | undefined, allowedMimeTypes: string[], label: string): Buffer {
    const normalizedMimeType = (mimeType || 'image/png').trim().toLowerCase();
    if (!allowedMimeTypes.includes(normalizedMimeType)) {
      throw new BadRequestException(`${label}图片格式不支持`);
    }
    const cleaned = base64.includes(',') ? base64.split(',').pop() || '' : base64;
    if (!cleaned.trim()) {
      throw new BadRequestException(`${label}图片内容不能为空`);
    }
    try {
      return Buffer.from(cleaned, 'base64');
    } catch {
      throw new BadRequestException(`${label}图片Base64解析失败`);
    }
  }

  private validatePngImage(buffer: Buffer, label: string): void {
    const pngSignature = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
    if (buffer.length < 24 || !buffer.subarray(0, 8).equals(pngSignature)) {
      throw new BadRequestException(`${label}必须是PNG文件`);
    }
    const width = buffer.readUInt32BE(16);
    const height = buffer.readUInt32BE(20);
    if (width <= 0 || height <= 0) {
      throw new BadRequestException(`${label}尺寸无效`);
    }
    if (width % CUSTOM_SKIN_HFRAMES !== 0 || height % CUSTOM_SKIN_VFRAMES !== 0) {
      throw new BadRequestException(`${label}必须是${CUSTOM_SKIN_HFRAMES}列x${CUSTOM_SKIN_VFRAMES}行PNG`);
    }
  }

  private createCustomSkinId(prefix: string, name: string): string {
    const namePart = (name || 'skin')
      .trim()
      .toLowerCase()
      .replace(/[^a-zA-Z0-9_]+/g, '_')
      .replace(/_+/g, '_')
      .replace(/^_+|_+$/g, '')
      .slice(0, 24) || 'skin';
    return `${prefix}_${namePart}_${randomUUID().replace(/-/g, '').slice(0, 12)}`;
  }

  private isCustomAssetSkinId(skinId: string): boolean {
    return skinId.startsWith('custom_') || skinId.startsWith('generated_');
  }

  private skinNameFromId(skinId: string): string {
    if (skinId.startsWith('generated_')) {
      return '生成角色';
    }
    if (skinId.startsWith('custom_')) {
      return '自定义角色';
    }
    return skinId;
  }

  private async writeSkinMetadata(userId: bigint, skinId: string, skinName: string, source: string): Promise<void> {
    const metadataPath = this.getAccountSkinMetadataPath(userId, skinId);
    await writeFile(metadataPath, JSON.stringify({
      skin_id: skinId,
      name: skinName,
      source,
      hframes: CUSTOM_SKIN_HFRAMES,
      vframes: CUSTOM_SKIN_VFRAMES,
      created_at: new Date().toISOString(),
    }, null, 2));
  }

  private getAccountAssetRoot(): string {
    return resolve(process.cwd(), this.configService.get<string>('ACCOUNT_ASSET_DIR') || GENERATED_ASSETS_DIR);
  }

  private getAccountSkinPath(userId: bigint, skinId: string): string {
    return join(this.getAccountAssetRoot(), userId.toString(), 'skins', `${skinId}.png`);
  }

  private getAccountSkinMetadataPath(userId: bigint, skinId: string): string {
    return join(this.getAccountAssetRoot(), userId.toString(), 'skins', `${skinId}.json`);
  }

  private getAccountSkinUrl(userId: bigint, skinId: string): string {
    return `${PUBLIC_ASSETS_PREFIX}/${userId.toString()}/skins/${skinId}.png`;
  }

  private readAccountAvatarBase64(userId: bigint, avatarUrl: string): string {
    if (!avatarUrl.startsWith(`${PUBLIC_ASSETS_PREFIX}/${userId.toString()}/`)) {
      return '';
    }
    const relativePath = avatarUrl.slice(PUBLIC_ASSETS_PREFIX.length + 1);
    if (relativePath.includes('..')) {
      return '';
    }
    const assetRoot = this.getAccountAssetRoot();
    const avatarPath = resolve(assetRoot, relativePath);
    const normalizedRoot = assetRoot.endsWith('/') ? assetRoot : `${assetRoot}/`;
    if (!avatarPath.startsWith(normalizedRoot)) {
      return '';
    }
    if (!existsSync(avatarPath)) {
      return '';
    }
    return readFileSync(avatarPath).toString('base64');
  }

  private extensionForMime(mimeType: string): string {
    switch (mimeType.toLowerCase()) {
      case 'image/jpeg':
        return 'jpg';
      case 'image/webp':
        return 'webp';
      default:
        return 'png';
    }
  }

  private getAccountSettings(profile: UserProfiles): AccountSettings {
    const tags = this.getProfileTags(profile);
    const storedSettings = tags[PROFILE_SETTINGS_TAG_KEY];
    return this.sanitizeAccountSettings(storedSettings && typeof storedSettings === 'object' && !Array.isArray(storedSettings)
      ? storedSettings as Record<string, unknown>
      : {});
  }

  private mergeProfileTagsWithSettings(profile: UserProfiles, incomingSettings: Record<string, unknown>): Record<string, any> {
    const tags = this.getProfileTags(profile);
    tags[PROFILE_SETTINGS_TAG_KEY] = this.sanitizeAccountSettings({
      ...this.getAccountSettings(profile),
      ...incomingSettings,
    });
    return tags;
  }

  private getProfileTags(profile: UserProfiles): Record<string, any> {
    if (!profile.tags || typeof profile.tags !== 'object' || Array.isArray(profile.tags)) {
      return {};
    }
    return { ...profile.tags };
  }

  private sanitizeAccountSettings(settings: Record<string, unknown>): AccountSettings {
    const sanitized: AccountSettings = { ...DEFAULT_ACCOUNT_SETTINGS };
    for (const key of Object.keys(DEFAULT_ACCOUNT_SETTINGS)) {
      if (!Object.prototype.hasOwnProperty.call(settings, key)) {
        continue;
      }
      const value = settings[key];
      if (ACCOUNT_SETTING_NUMBER_KEYS.has(key)) {
        const numberValue = typeof value === 'number' ? value : Number(value);
        if (Number.isFinite(numberValue)) {
          sanitized[key] = key === 'ui_scale'
            ? Math.min(1.2, Math.max(0.8, numberValue))
            : Math.min(1, Math.max(0, numberValue));
        }
      } else if (ACCOUNT_SETTING_BOOLEAN_KEYS.has(key)) {
        sanitized[key] = typeof value === 'boolean' ? value : value === 'true' || value === 1 || value === '1';
      } else if (key === 'room_visit_policy' && typeof value === 'string' && ROOM_VISIT_POLICIES.has(value as RoomVisitPolicy)) {
        sanitized[key] = value as RoomVisitPolicy;
      }
    }
    return sanitized;
  }
}
