import { type UserRole, type KYCStatus } from '@prisma/client';
import { AggregateRoot } from '@modules/shared';
import { UserDeactivatedEvent } from '../events/user-deactivated.event';
import { UserKycUpdatedEvent } from '../events/user-kyc-updated.event';
import { UserRegisteredEvent } from '../events/user-registered.event';
import { type Email } from '../value-objects/email.vo';
import { type HashedPassword } from '../value-objects/hashed-password.vo';
import { type Phone } from '../value-objects/phone.vo';

export interface UserProps {
  email: Email | null;
  phone: Phone;
  passwordHash: HashedPassword | null;
  fullName: string;
  avatarUrl: string | null;
  role: UserRole;
  kycStatus: KYCStatus;
  kycData: unknown;
  isActive: boolean;
  deletedAt: Date | null;
  totpSecret: string | null;
  totpEnabled: boolean;
  totpBackupCodes: string[];
  totpEnabledAt: Date | null;
}

export class UserEntity extends AggregateRoot<string> {
  private _email: Email | null;
  private _phone: Phone;
  private _passwordHash: HashedPassword | null;
  private _fullName: string;
  private _avatarUrl: string | null;
  private _role: UserRole;
  private _kycStatus: KYCStatus;
  private _kycData: unknown;
  private _isActive: boolean;
  private _deletedAt: Date | null;
  private _totpSecret: string | null;
  private _totpEnabled: boolean;
  private _totpBackupCodes: string[];
  private _totpEnabledAt: Date | null;

  constructor(id: string, props: UserProps, createdAt?: Date, updatedAt?: Date) {
    super(id, createdAt, updatedAt);
    this._email = props.email;
    this._phone = props.phone;
    this._passwordHash = props.passwordHash;
    this._fullName = props.fullName;
    this._avatarUrl = props.avatarUrl;
    this._role = props.role;
    this._kycStatus = props.kycStatus;
    this._kycData = props.kycData;
    this._isActive = props.isActive;
    this._deletedAt = props.deletedAt;
    this._totpSecret = props.totpSecret;
    this._totpEnabled = props.totpEnabled;
    this._totpBackupCodes = props.totpBackupCodes;
    this._totpEnabledAt = props.totpEnabledAt;
  }

  get email(): Email | null { return this._email; }
  get phone(): Phone { return this._phone; }
  get passwordHash(): HashedPassword | null { return this._passwordHash; }
  get fullName(): string { return this._fullName; }
  get avatarUrl(): string | null { return this._avatarUrl; }
  get role(): UserRole { return this._role; }
  get kycStatus(): KYCStatus { return this._kycStatus; }
  get kycData(): unknown { return this._kycData; }
  get isActive(): boolean { return this._isActive; }
  get deletedAt(): Date | null { return this._deletedAt; }
  get totpSecret(): string | null { return this._totpSecret; }
  get totpEnabled(): boolean { return this._totpEnabled; }
  get totpBackupCodes(): string[] { return this._totpBackupCodes; }
  get totpEnabledAt(): Date | null { return this._totpEnabledAt; }

  static createNew(
    id: string,
    phone: Phone,
    fullName: string,
    passwordHash: HashedPassword,
    email?: Email,
    role: UserRole = 'BUYER',
  ): UserEntity {
    const user = new UserEntity(id, {
      email: email ?? null,
      phone,
      passwordHash,
      fullName,
      avatarUrl: null,
      role,
      kycStatus: 'NONE',
      kycData: null,
      isActive: true,
      deletedAt: null,
      totpSecret: null,
      totpEnabled: false,
      totpBackupCodes: [],
      totpEnabledAt: null,
    });

    user.addDomainEvent(new UserRegisteredEvent(id, phone.value, role));
    return user;
  }

  /**
   * Create a passwordless user (e.g. via Phone-OTP login auto-register).
   * `passwordHash` is null so password login is not possible until the user
   * sets one via the password-reset / profile flow. A fullName fallback is
   * used since OTP signup does not collect a name.
   */
  static createPasswordless(
    id: string,
    phone: Phone,
    fullName?: string,
    role: UserRole = 'BUYER',
  ): UserEntity {
    const displayName =
      fullName && fullName.trim().length > 0
        ? fullName
        : `Người dùng ${phone.value.slice(-4)}`;
    const user = new UserEntity(id, {
      email: null,
      phone,
      passwordHash: null,
      fullName: displayName,
      avatarUrl: null,
      role,
      kycStatus: 'NONE',
      kycData: null,
      isActive: true,
      deletedAt: null,
      totpSecret: null,
      totpEnabled: false,
      totpBackupCodes: [],
      totpEnabledAt: null,
    });

    user.addDomainEvent(new UserRegisteredEvent(id, phone.value, role));
    return user;
  }

  updateKycStatus(status: KYCStatus, kycData?: unknown): void {
    const previousStatus = this._kycStatus;
    this._kycStatus = status;
    if (kycData !== undefined) this._kycData = kycData;
    this.updatedAt = new Date();

    this.addDomainEvent(new UserKycUpdatedEvent(this.id, status, previousStatus));
  }

  deactivate(): void {
    this._isActive = false;
    this.updatedAt = new Date();

    this.addDomainEvent(new UserDeactivatedEvent(this.id));
  }

  activate(): void {
    this._isActive = true;
    this.updatedAt = new Date();
  }

  enableTotp(secret: string, backupCodes: string[]): void {
    this._totpSecret = secret;
    this._totpEnabled = true;
    this._totpBackupCodes = backupCodes;
    this._totpEnabledAt = new Date();
    this.updatedAt = new Date();
  }

  disableTotp(): void {
    this._totpSecret = null;
    this._totpEnabled = false;
    this._totpBackupCodes = [];
    this._totpEnabledAt = null;
    this.updatedAt = new Date();
  }

  consumeBackupCode(index: number): void {
    this._totpBackupCodes = this._totpBackupCodes.filter((_, i) => i !== index);
    this.updatedAt = new Date();
  }

  updateProfile(fullName?: string, avatarUrl?: string | null, email?: Email | null): void {
    if (fullName !== undefined) this._fullName = fullName;
    if (avatarUrl !== undefined) this._avatarUrl = avatarUrl;
    if (email !== undefined) this._email = email;
    this.updatedAt = new Date();
  }

  updatePhone(phone: Phone): void {
    this._phone = phone;
    this.updatedAt = new Date();
  }

  changePassword(passwordHash: HashedPassword): void {
    this._passwordHash = passwordHash;
    this.updatedAt = new Date();
  }
}
