import { Entity } from '@core/shared/domain/Entity';
import { AdminDomainInvariantError, AdminDomainValidationError } from '../errors/AdminDomainError';
import { Email } from '../value-objects/Email';
import { UserId } from '../value-objects/UserId';
import { UserRole } from '../value-objects/UserRole';
import { UserStatus } from '../value-objects/UserStatus';

export interface AdminUserProps {
  id: UserId;
  email: Email;
  roles: UserRole[];
  status: UserStatus;
  displayName: string;
  createdAt: Date;
  updatedAt: Date;
  lastLoginAt: Date | undefined;
  primaryDriverId: string | undefined;
}

export class AdminUser extends Entity<UserId> {
  private _email: Email;
  private _roles: UserRole[];
  private _status: UserStatus;
  private _displayName: string;
  private _createdAt: Date;
  private _updatedAt: Date;
  private _lastLoginAt: Date | undefined;
  private _primaryDriverId: string | undefined;

  private constructor(props: AdminUserProps) {
    super(props.id);
    
    this._email = props.email;
    this._roles = props.roles;
    this._status = props.status;
    this._displayName = props.displayName;
    this._createdAt = props.createdAt;
    this._updatedAt = props.updatedAt;
    this._lastLoginAt = props.lastLoginAt;
    this._primaryDriverId = props.primaryDriverId;
  }

  /**
   * Factory method to create a new AdminUser
   * Validates all business rules and invariants
   */
  static create(props: {
    id: string;
    email: string;
    roles: string[];
    status: string;
    displayName: string;
    createdAt?: Date;
    updatedAt?: Date;
    lastLoginAt?: Date;
    primaryDriverId?: string;
  }): AdminUser {
    // Validate required fields
    if (!props.id || props.id.trim().length === 0) {
      throw new AdminDomainValidationError('User ID is required');
    }

    if (!props.email || props.email.trim().length === 0) {
      throw new AdminDomainValidationError('Email is required');
    }

    if (!props.roles || props.roles.length === 0) {
      throw new AdminDomainValidationError('At least one role is required');
    }

    if (!props.status || props.status.trim().length === 0) {
      throw new AdminDomainValidationError('Status is required');
    }

    if (!props.displayName || props.displayName.trim().length === 0) {
      throw new AdminDomainValidationError('Display name is required');
    }

    // Validate display name length
    const trimmedName = props.displayName.trim();
    if (trimmedName.length < 2 || trimmedName.length > 100) {
      throw new AdminDomainValidationError('Display name must be between 2 and 100 characters');
    }

    // Create value objects
    const id = UserId.fromString(props.id);
    const email = Email.fromString(props.email);
    const roles = props.roles.map(role => UserRole.fromString(role));
    const status = UserStatus.fromString(props.status);

    // Validate role hierarchy - ensure no duplicate roles
    const uniqueRoles = new Set(roles.map(r => r.toString()));
    if (uniqueRoles.size !== roles.length) {
      throw new AdminDomainValidationError('Duplicate roles are not allowed');
    }

    const now = props.createdAt ?? new Date();

    return new AdminUser({
      id,
      email,
      roles,
      status,
      displayName: trimmedName,
      createdAt: now,
      updatedAt: props.updatedAt ?? now,
      lastLoginAt: props.lastLoginAt ?? undefined,
      primaryDriverId: props.primaryDriverId ?? undefined,
    });
  }

  /**
   * Rehydrate from storage
   */
  static rehydrate(props: {
    id: string;
    email: string;
    roles: string[];
    status: string;
    displayName: string;
    createdAt: Date;
    updatedAt: Date;
    lastLoginAt?: Date;
    primaryDriverId?: string;
  }): AdminUser {
    return this.create(props);
  }

  // Getters
  get email(): Email {
    return this._email;
  }

  get roles(): UserRole[] {
    return [...this._roles];
  }

  get status(): UserStatus {
    return this._status;
  }

  get displayName(): string {
    return this._displayName;
  }

  get createdAt(): Date {
    return new Date(this._createdAt.getTime());
  }

  get updatedAt(): Date {
    return new Date(this._updatedAt.getTime());
  }

  get lastLoginAt(): Date | undefined {
    return this._lastLoginAt ? new Date(this._lastLoginAt.getTime()) : undefined;
  }

  get primaryDriverId(): string | undefined {
    return this._primaryDriverId;
  }

  // Domain methods

  /**
   * Add a role to the user
   * Cannot add duplicate roles
   * Cannot add owner role if user already has other roles
   */
  addRole(role: UserRole): void {
    if (this._roles.some(r => r.equals(role))) {
      throw new AdminDomainInvariantError(`Role ${role.value} is already assigned`);
    }

    // If adding owner role, user must have no other roles
    if (role.value === 'owner' && this._roles.length > 0) {
      throw new AdminDomainInvariantError('Cannot add owner role to user with existing roles');
    }

    // If user has owner role, cannot add other roles
    if (this._roles.some(r => r.value === 'owner')) {
      throw new AdminDomainInvariantError('Owner cannot have additional roles');
    }

    this._roles.push(role);
    this._updatedAt = new Date();
  }

  /**
   * Remove a role from the user
   * Cannot remove the last role
   * Cannot remove owner role (must be transferred first)
   */
  removeRole(role: UserRole): void {
    const roleIndex = this._roles.findIndex(r => r.equals(role));
    
    if (roleIndex === -1) {
      throw new AdminDomainInvariantError(`Role ${role.value} not found`);
    }

    if (this._roles.length === 1) {
      throw new AdminDomainInvariantError('Cannot remove the last role from user');
    }

    if (role.value === 'owner') {
      throw new AdminDomainInvariantError('Cannot remove owner role. Transfer ownership first.');
    }

    this._roles.splice(roleIndex, 1);
    this._updatedAt = new Date();
  }

  /**
   * Update user status
   */
  updateStatus(newStatus: UserStatus): void {
    if (this._status.equals(newStatus)) {
      throw new AdminDomainInvariantError(`User already has status ${newStatus.value}`);
    }

    this._status = newStatus;
    this._updatedAt = new Date();
  }

  /**
   * Check if user has a specific role
   */
  hasRole(roleValue: string): boolean {
    return this._roles.some(r => r.value === roleValue);
  }

  /**
   * Check if user is a system administrator
   */
  isSystemAdmin(): boolean {
    return this._roles.some(r => r.isSystemAdmin());
  }

  /**
   * Check if user has higher authority than another user
   */
  hasHigherAuthorityThan(other: AdminUser): boolean {
    // Get highest role for each user
    const hierarchy: Record<string, number> = {
      user: 0,
      admin: 1,
      owner: 2,
    };

    const myHighest = Math.max(...this._roles.map(r => hierarchy[r.value] ?? 0));
    const otherHighest = Math.max(...other._roles.map(r => hierarchy[r.value] ?? 0));

    return myHighest > otherHighest;
  }

  /**
   * Update last login timestamp
   */
  recordLogin(): void {
    this._lastLoginAt = new Date();
    this._updatedAt = new Date();
  }

  /**
   * Update display name (only for admin operations)
   */
  updateDisplayName(newName: string): void {
    const trimmed = newName.trim();
    
    if (trimmed.length < 2 || trimmed.length > 100) {
      throw new AdminDomainValidationError('Display name must be between 2 and 100 characters');
    }

    this._displayName = trimmed;
    this._updatedAt = new Date();
  }

  /**
   * Update email
   */
  updateEmail(newEmail: Email): void {
    if (this._email.equals(newEmail)) {
      throw new AdminDomainInvariantError('Email is already the same');
    }

    this._email = newEmail;
    this._updatedAt = new Date();
  }

  /**
   * Check if user is active
   */
  isActive(): boolean {
    return this._status.isActive();
  }

  /**
   * Suspend user
   */
  suspend(): void {
    if (this._status.isSuspended()) {
      throw new AdminDomainInvariantError('User is already suspended');
    }

    if (this._status.isDeleted()) {
      throw new AdminDomainInvariantError('Cannot suspend a deleted user');
    }

    this._status = UserStatus.create('suspended');
    this._updatedAt = new Date();
  }

  /**
   * Activate user
   */
  activate(): void {
    if (this._status.isActive()) {
      throw new AdminDomainInvariantError('User is already active');
    }

    if (this._status.isDeleted()) {
      throw new AdminDomainInvariantError('Cannot activate a deleted user');
    }

    this._status = UserStatus.create('active');
    this._updatedAt = new Date();
  }

  /**
   * Soft delete user
   */
  delete(): void {
    if (this._status.isDeleted()) {
      throw new AdminDomainInvariantError('User is already deleted');
    }

    this._status = UserStatus.create('deleted');
    this._updatedAt = new Date();
  }

  /**
   * Get role display names
   */
  getRoleDisplayNames(): string[] {
    return this._roles.map(r => {
      switch (r.value) {
        case 'owner': return 'Owner';
        case 'admin': return 'Admin';
        case 'user': return 'User';
        default: return r.value;
      }
    });
  }

  /**
   * Check if this user can manage another user
   * Owner can manage everyone (including self)
   * Admin can manage users but not admins/owners (including self)
   * User can manage self only
   */
  canManage(target: AdminUser): boolean {
    // Owner can manage everyone
    if (this.hasRole('owner')) {
      return true;
    }

    // Admin can manage non-admin users
    if (this.hasRole('admin')) {
      // Cannot manage admins/owners (including self)
      if (target.isSystemAdmin()) {
        return false;
      }
      // Can manage non-admin users
      return true;
    }

    // User can only manage self
    return this.id.equals(target.id);
  }

  /**
   * Check if this user can modify roles of target user
   * Only owner can modify roles
   */
  canModifyRoles(target: AdminUser): boolean {
    // Only owner can modify roles
    if (!this.hasRole('owner')) {
      return false;
    }

    // Cannot modify own roles (prevents accidental lockout)
    if (this.id.equals(target.id)) {
      return false;
    }

    return true;
  }

  /**
   * Check if this user can change status of target user
   * Owner can change anyone's status
   * Admin can change user status but not other admins/owners
   */
  canChangeStatus(target: AdminUser): boolean {
    if (this.id.equals(target.id)) {
      return false; // Cannot change own status
    }

    if (this.hasRole('owner')) {
      return true;
    }

    if (this.hasRole('admin')) {
      return !target.isSystemAdmin();
    }

    return false;
  }

  /**
   * Check if this user can delete target user
   * Owner can delete anyone except self
   * Admin can delete users but not admins/owners
   */
  canDelete(target: AdminUser): boolean {
    if (this.id.equals(target.id)) {
      return false; // Cannot delete self
    }

    if (this.hasRole('owner')) {
      return true;
    }

    if (this.hasRole('admin')) {
      return !target.isSystemAdmin();
    }

    return false;
  }

  /**
   * Get summary for display
   */
  toSummary(): {
    id: string;
    email: string;
    displayName: string;
    roles: string[];
    status: string;
    isSystemAdmin: boolean;
    lastLoginAt?: Date;
  } {
    const summary: {
      id: string;
      email: string;
      displayName: string;
      roles: string[];
      status: string;
      isSystemAdmin: boolean;
      lastLoginAt?: Date;
    } = {
      id: this.id.value,
      email: this._email.value,
      displayName: this._displayName,
      roles: this._roles.map(r => r.value),
      status: this._status.value,
      isSystemAdmin: this.isSystemAdmin(),
    };

    if (this._lastLoginAt) {
      summary.lastLoginAt = this._lastLoginAt;
    }

    return summary;
  }

  /**
   * Equals comparison
   */
  equals(other?: AdminUser): boolean {
    if (!other) {
      return false;
    }
    return this.id.equals(other.id);
  }
}