import '@angular/compiler';
import {
  describe,
  it,
  expect,
  vi
} from 'vitest';
import {
  Injector,
  runInInjectionContext,
  signal
} from '@angular/core';
import { Router } from '@angular/router';
import { Store } from '@ngrx/store';
import { of } from 'rxjs';

import { DashboardComponent } from './dashboard.component';
import { ViewportService } from '../../core/platform';
import { ServerDirectoryFacade } from '../../domains/server-directory/application/facades/server-directory.facade';
import { FriendService } from '../../domains/direct-message/application/services/friend.service';
import { RoomsActions } from '../../store/rooms/rooms.actions';
import {
  selectSearchResults,
  selectIsSearching,
  selectSavedRooms
} from '../../store/rooms/rooms.selectors';
import { selectAllUsers, selectCurrentUser } from '../../store/users/users.selectors';
import type { ServerInfo } from '../../domains/server-directory/domain/models/server-directory.model';
import type { Room, User } from '../../shared-kernel';
import { AppI18nService } from '../../core/i18n';

interface HarnessOptions {
  searchResults?: ServerInfo[];
  saved?: Room[];
  users?: User[];
  currentUser?: User | null;
  featured?: ServerInfo[];
  trending?: ServerInfo[];
  friendIds?: string[];
  isMobile?: boolean;
}

function makeServer(id: string, name = id): ServerInfo {
  return { id, name, maxUsers: 50, userCount: 1, isPrivate: false } as unknown as ServerInfo;
}

function createHarness(options: HarnessOptions = {}) {
  const dispatch = vi.fn();
  const searchResultsSig = signal<ServerInfo[]>(options.searchResults ?? []);
  const savedSig = signal<Room[]>(options.saved ?? []);
  const usersSig = signal<User[]>(options.users ?? []);
  const currentUserSig = signal<User | null>(options.currentUser ?? null);
  const store = {
    selectSignal: (selector: unknown) => {
      if (selector === selectSearchResults) {
        return searchResultsSig;
      }

      if (selector === selectIsSearching) {
        return signal(false);
      }

      if (selector === selectSavedRooms) {
        return savedSig;
      }

      if (selector === selectAllUsers) {
        return usersSig;
      }

      if (selector === selectCurrentUser) {
        return currentUserSig;
      }

      return signal(null);
    },
    dispatch
  } as unknown as Store;
  const router = { navigate: vi.fn() } as unknown as Router;
  const getFeaturedServers = vi.fn(() => of(options.featured ?? []));
  const getTrendingServers = vi.fn(() => of(options.trending ?? []));
  const serverDirectory = { getFeaturedServers, getTrendingServers } as unknown as ServerDirectoryFacade;
  const friendIds = new Set<string>(options.friendIds ?? []);
  const friendService = { friendIds: () => friendIds, friends: () => [] } as unknown as FriendService;
  const appI18n = {
    instant: (key: string, params?: Record<string, unknown>) => {
      if (key === 'dashboard.serverMeta.member') {
        return `${params?.count} member`;
      }

      if (key === 'dashboard.serverMeta.members') {
        return `${params?.count} members`;
      }

      return key;
    }
  } as unknown as AppI18nService;
  const injector = Injector.create({
    providers: [
      DashboardComponent,
      { provide: Store, useValue: store },
      { provide: Router, useValue: router },
      { provide: ServerDirectoryFacade, useValue: serverDirectory },
      { provide: FriendService, useValue: friendService },
      { provide: ViewportService, useValue: { isMobile: signal(options.isMobile ?? false) } },
      { provide: AppI18nService, useValue: appI18n }
    ]
  });
  const component = runInInjectionContext(injector, () => injector.get(DashboardComponent));

  return {
    component,
    dispatch,
    router,
    getFeaturedServers,
    getTrendingServers
  };
}

describe('DashboardComponent', () => {
  it('exposes the mobile viewport flag', () => {
    expect(createHarness().component.isMobile()).toBe(false);
    expect(createHarness({ isMobile: true }).component.isMobile()).toBe(true);
  });

  it('reports a new user when there are no servers or users', () => {
    const { component } = createHarness();

    expect(component.isNewUser()).toBe(true);
  });

  it('filters people by the active query', () => {
    const { component } = createHarness({
      users: [{ id: 'u1', displayName: 'Alice' } as unknown as User, { id: 'u2', displayName: 'Bob' } as unknown as User]
    });

    component.onSearchChange('ali');

    expect(component.topPeopleResults().map((user) => user.id)).toEqual(['u1']);
  });

  it('limits server quick results', () => {
    const { component } = createHarness({
      searchResults: Array.from({ length: 8 }, (_, index) => makeServer(`s${index}`))
    });

    component.onSearchChange('s');

    expect(component.topServerResults()).toHaveLength(5);
  });

  it('exposes an invite result for invite-like queries', () => {
    const { component } = createHarness();

    component.onSearchChange('https://app.test/invite/Code_42');

    expect(component.inviteResult()).toEqual({ inviteId: 'Code_42' });
  });

  it('opens a joined server in place and routes others to the servers page', () => {
    const joined = { id: 's1', name: 'Joined' } as unknown as Room;
    const { component, dispatch, router } = createHarness({ saved: [joined] });

    component.openServer(makeServer('s1', 'Joined'));
    expect(dispatch).toHaveBeenCalledWith(RoomsActions.viewServer({ room: joined }));

    component.openServer(makeServer('s2', 'Other'));
    expect(router.navigate).toHaveBeenCalledWith(['/servers']);
  });

  it('navigates to the invite route when opening an invite', () => {
    const { component, router } = createHarness();

    component.onSearchChange('abc123');
    component.openInvite();

    expect(router.navigate).toHaveBeenCalledWith(['/invite', 'abc123'], { queryParams: undefined });
  });

  it('forwards the signal server when opening a full invite URL', () => {
    const { component, router } = createHarness();

    component.onSearchChange('https://web.toju.app/invite/Code_42?server=https%3A%2F%2Fsignal.toju.app');
    component.openInvite();

    expect(router.navigate).toHaveBeenCalledWith(['/invite', 'Code_42'], {
      queryParams: { server: 'https://signal.toju.app' }
    });
  });

  it('suggests people you might know independent of the query, excluding self', () => {
    const { component } = createHarness({
      users: [{ id: 'u1', oderId: 'u1', displayName: 'Alice' } as unknown as User, { id: 'u2', oderId: 'u2', displayName: 'Bob' } as unknown as User],
      currentUser: { id: 'u1', oderId: 'u1' } as unknown as User
    });

    expect(component.peopleYouMightKnow().map((user) => user.id)).toEqual(['u2']);
  });

  it('loads popular servers from featured results on init', () => {
    const featured = [makeServer('f1'), makeServer('f2')];
    const { component } = createHarness({ featured });

    component.ngOnInit();

    expect(component.popularServers().map((server) => server.id)).toEqual(['f1', 'f2']);
  });

  it('falls back to trending servers when featured is empty', () => {
    const trending = [makeServer('t1')];
    const { component } = createHarness({ featured: [], trending });

    component.ngOnInit();

    expect(component.popularServers().map((server) => server.id)).toEqual(['t1']);
  });

  it('limits recently active servers to the discovery cap', () => {
    const saved = Array.from({ length: 9 }, (_, index) => ({ id: `r${index}`, name: `Room ${index}`, userCount: 1 }) as unknown as Room);
    const { component } = createHarness({ saved });

    expect(component.recentlyActiveServers()).toHaveLength(5);
  });

  it('excludes existing friends from people you might know and lists them under friends', () => {
    const { component } = createHarness({
      users: [
        { id: 'u1', oderId: 'u1', displayName: 'Alice' } as unknown as User,
        { id: 'u2', oderId: 'u2', displayName: 'Bob' } as unknown as User,
        { id: 'u3', oderId: 'u3', displayName: 'Cara' } as unknown as User
      ],
      currentUser: { id: 'u1', oderId: 'u1' } as unknown as User,
      friendIds: ['u2']
    });

    expect(component.peopleYouMightKnow().map((user) => user.id)).toEqual(['u3']);
    expect(component.friends().map((user) => user.id)).toEqual(['u2']);
  });

  it('records, removes, and clears recent searches', () => {
    const { component } = createHarness();

    component.onSearchChange('gaming');
    component.submitSearch();
    component.onSearchChange('music');
    component.submitSearch();

    expect(component.recentSearches()).toEqual(['music', 'gaming']);

    component.removeRecentSearch('gaming');
    expect(component.recentSearches()).toEqual(['music']);

    component.clearRecentSearches();
    expect(component.recentSearches()).toEqual([]);
  });

  it('deduplicates recent searches and keeps the most recent first', () => {
    const { component } = createHarness();

    component.onSearchChange('gaming');
    component.submitSearch();
    component.onSearchChange('music');
    component.submitSearch();
    component.onSearchChange('gaming');
    component.submitSearch();

    expect(component.recentSearches()).toEqual(['gaming', 'music']);
  });
});
