import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { RouterLink } from '@angular/router';
import { firstValueFrom } from 'rxjs';
import {
  AdminUsersService,
  type AdminUsersPage,
  type AdminUsersQuery,
} from './admin-users.service';

/** Page-size options. Matches the BFF's `DEFAULT_LIMIT` (50) and `MAX_LIMIT` (200). */
const PAGE_SIZES = [25, 50, 100, 200] as const;

/**
 * User directory viewer per ADR-0020's v1 admin catalogue. Consumes
 * `GET /api/admin/users` from the BFF (PR #141) and renders:
 *
 *   - Filter row: username prefix, displayName contains,
 *     audience, last-seen-at range, page-size selector.
 *   - Paginated table with `oid`, `displayName`, `username`,
 *     `audience`, `firstSeenAt`, `lastSeenAt` columns.
 *   - Pagination controls + loading / empty / error states.
 *
 * Mirrors the `/audit` page (PR #136) in shape and signal layout
 * so a future contributor lands familiar code regardless of which
 * admin module they open first.
 */
@Component({
  selector: 'app-users-page',
  imports: [FormsModule, RouterLink],
  templateUrl: './users.html',
  styleUrl: './users.scss',
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UsersPage {
  private readonly service = inject(AdminUsersService);

  protected readonly pageSizes = PAGE_SIZES;

  protected readonly username = signal('');
  protected readonly displayName = signal('');
  protected readonly audience = signal<'' | 'workforce' | 'customer'>('');
  protected readonly lastSeenAtFrom = signal('');
  protected readonly lastSeenAtTo = signal('');

  protected readonly limit = signal<number>(50);
  protected readonly offset = signal<number>(0);

  protected readonly page = signal<AdminUsersPage | null>(null);
  protected readonly loading = signal(false);
  protected readonly error = signal<string | null>(null);

  protected readonly hasNextPage = computed(() => {
    const p = this.page();
    if (p === null) return false;
    return p.offset + p.items.length < p.total;
  });

  protected readonly hasPreviousPage = computed(() => {
    const p = this.page();
    if (p === null) return false;
    return p.offset > 0;
  });

  protected readonly resultRange = computed(() => {
    const p = this.page();
    if (p === null) return '';
    if (p.items.length === 0) return p.total === 0 ? 'No users' : `0 of ${p.total}`;
    const first = p.offset + 1;
    const last = p.offset + p.items.length;
    return `${first}–${last} of ${p.total}`;
  });

  async search(): Promise<void> {
    this.offset.set(0);
    await this.fetch();
  }

  async next(): Promise<void> {
    if (!this.hasNextPage()) return;
    this.offset.update((o) => o + this.limit());
    await this.fetch();
  }

  async previous(): Promise<void> {
    if (!this.hasPreviousPage()) return;
    this.offset.update((o) => Math.max(0, o - this.limit()));
    await this.fetch();
  }

  async clearFilters(): Promise<void> {
    this.username.set('');
    this.displayName.set('');
    this.audience.set('');
    this.lastSeenAtFrom.set('');
    this.lastSeenAtTo.set('');
    this.offset.set(0);
    await this.fetch();
  }

  protected formatTimestamp(iso: string): string {
    try {
      return new Date(iso).toLocaleString();
    } catch {
      return iso;
    }
  }

  private async fetch(): Promise<void> {
    this.loading.set(true);
    this.error.set(null);
    try {
      const page = await firstValueFrom(this.service.query(this.buildFilters()));
      this.page.set(page);
    } catch (err) {
      const status = errorStatus(err);
      this.error.set(
        status === 403
          ? 'You do not have access to the user directory on this session.'
          : 'Could not load the user directory. Try again in a moment.',
      );
      this.page.set(null);
    } finally {
      this.loading.set(false);
    }
  }

  private buildFilters(): AdminUsersQuery {
    return {
      username: this.username().trim() || undefined,
      displayName: this.displayName().trim() || undefined,
      audience: this.audience() || undefined,
      lastSeenAtFrom: this.lastSeenAtFrom() ? toIso(this.lastSeenAtFrom()) : undefined,
      lastSeenAtTo: this.lastSeenAtTo() ? toIso(this.lastSeenAtTo()) : undefined,
      limit: this.limit(),
      offset: this.offset(),
    };
  }

  ngOnInit(): void {
    void this.fetch();
  }
}

function toIso(localValue: string): string {
  // `<input type="datetime-local">` emits `YYYY-MM-DDTHH:mm` (no
  // timezone). Treat as the user's local time and convert to ISO
  // 8601 so the BFF's IsISO8601 validator accepts it.
  const d = new Date(localValue);
  return Number.isNaN(d.getTime()) ? localValue : d.toISOString();
}

function errorStatus(err: unknown): number | null {
  if (typeof err === 'object' && err !== null && 'status' in err) {
    const s = (err as { status: unknown }).status;
    return typeof s === 'number' ? s : null;
  }
  return null;
}
