import {
  Injectable,
  inject,
  signal
} from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import {
  Observable,
  firstValueFrom,
  throwError
} from 'rxjs';
import { catchError, map } from 'rxjs/operators';
import {
  ServerDirectoryFacade,
  type RoomSignalSourceInput,
  type ServerSourceSelector
} from '../../../server-directory';

export interface KlipyGif {
  id: string;
  slug: string;
  title?: string;
  url: string;
  previewUrl: string;
  width: number;
  height: number;
}

interface KlipyAvailabilityResponse {
  enabled: boolean;
}

export interface KlipyGifSearchResponse {
  enabled: boolean;
  results: KlipyGif[];
  hasNext: boolean;
}

const DEFAULT_PAGE_SIZE = 24;
const KLIPY_CUSTOMER_ID_STORAGE_KEY = 'metoyou_klipy_customer_id';
const DEFAULT_AVAILABILITY_KEY = 'default';

interface KlipyAvailabilityState {
  enabled: boolean;
  loading: boolean;
}

@Injectable({ providedIn: 'root' })
export class KlipyService {
  private readonly http = inject(HttpClient);
  private readonly serverDirectory = inject(ServerDirectoryFacade);
  private readonly availabilityByKey = signal<Record<string, KlipyAvailabilityState>>({});

  isEnabled(source?: RoomSignalSourceInput | null): boolean {
    return this.getAvailabilityState(source).enabled;
  }

  isLoading(source?: RoomSignalSourceInput | null): boolean {
    return this.getAvailabilityState(source).loading;
  }

  async refreshAvailability(source?: RoomSignalSourceInput | null): Promise<void> {
    const selector = this.getSourceSelector(source);
    const key = this.getAvailabilityKey(selector);

    this.setAvailabilityState(key, { enabled: false,
      loading: true });

    try {
      const response = await firstValueFrom(
        this.http.get<KlipyAvailabilityResponse>(
          `${this.serverDirectory.getApiBaseUrl(selector)}/klipy/config`
        )
      );

      this.setAvailabilityState(key, {
        enabled: response.enabled === true,
        loading: false
      });
    } catch {
      this.setAvailabilityState(key, { enabled: false,
        loading: false });
    }
  }

  searchGifs(
    query: string,
    page = 1,
    perPage = DEFAULT_PAGE_SIZE,
    source?: RoomSignalSourceInput | null
  ): Observable<KlipyGifSearchResponse> {
    const selector = this.getSourceSelector(source);

    let params = new HttpParams()
      .set('page', String(Math.max(1, Math.floor(page))))
      .set('per_page', String(Math.max(1, Math.floor(perPage))))
      .set('customer_id', this.getOrCreateCustomerId());

    const trimmedQuery = query.trim();

    if (trimmedQuery) {
      params = params.set('q', trimmedQuery);
    }

    const locale = this.getPreferredLocale();

    if (locale) {
      params = params.set('locale', locale);
    }

    return this.http
      .get<KlipyGifSearchResponse>(`${this.serverDirectory.getApiBaseUrl(selector)}/klipy/gifs`, { params })
      .pipe(
        map((response) => ({
          enabled: response.enabled !== false,
          results: Array.isArray(response.results) ? response.results : [],
          hasNext: response.hasNext === true
        })),
        catchError((error) =>
          throwError(() => new Error(this.extractErrorMessage(error)))
        )
      );
  }

  normalizeMediaUrl(url: string): string {
    const trimmed = url.trim();

    if (!trimmed)
      return '';

    if (trimmed.startsWith('//'))
      return `https:${trimmed}`;

    return trimmed;
  }

  buildRenderableImageUrl(url: string): string {
    return this.normalizeMediaUrl(url);
  }

  buildImageProxyUrl(url: string, source?: RoomSignalSourceInput | null): string {
    const trimmed = this.normalizeMediaUrl(url);

    if (!trimmed)
      return '';

    if (!/^https?:\/\//i.test(trimmed))
      return trimmed;

    return `${this.serverDirectory.getApiBaseUrl(this.getSourceSelector(source))}/image-proxy?url=${encodeURIComponent(trimmed)}`;
  }

  private getAvailabilityState(source?: RoomSignalSourceInput | null): KlipyAvailabilityState {
    return this.availabilityByKey()[this.getAvailabilityKey(this.getSourceSelector(source))]
      ?? { enabled: false,
        loading: true };
  }

  private setAvailabilityState(key: string, state: KlipyAvailabilityState): void {
    this.availabilityByKey.update((availabilityByKey) => ({
      ...availabilityByKey,
      [key]: state
    }));
  }

  private getSourceSelector(source?: RoomSignalSourceInput | null): ServerSourceSelector | undefined {
    return this.serverDirectory.buildRoomSignalSelector(source ?? undefined);
  }

  private getAvailabilityKey(selector?: ServerSourceSelector): string {
    if (selector?.sourceId) {
      return `id:${selector.sourceId}`;
    }

    if (selector?.sourceUrl) {
      return `url:${selector.sourceUrl}`;
    }

    return DEFAULT_AVAILABILITY_KEY;
  }

  private getPreferredLocale(): string | null {
    if (typeof navigator === 'undefined' || !navigator.language)
      return null;

    const locale = navigator.language.trim();

    return locale || null;
  }

  private getOrCreateCustomerId(): string {
    if (typeof window === 'undefined') {
      return 'server';
    }

    try {
      const existing = window.localStorage.getItem(KLIPY_CUSTOMER_ID_STORAGE_KEY);

      if (existing?.trim())
        return existing;

      const created = window.crypto?.randomUUID?.()
        ?? `klipy-${Date.now().toString(36)}-${Math.random().toString(36)
          .slice(2, 10)}`;

      window.localStorage.setItem(KLIPY_CUSTOMER_ID_STORAGE_KEY, created);
      return created;
    } catch {
      return `klipy-${Date.now().toString(36)}`;
    }
  }

  private extractErrorMessage(error: unknown): string {
    const httpError = error as {
      error?: {
        error?: unknown;
        message?: unknown;
      };
      message?: unknown;
    };

    if (typeof httpError?.error?.error === 'string')
      return httpError.error.error;

    if (typeof httpError?.error?.message === 'string')
      return httpError.error.message;

    if (typeof httpError?.message === 'string')
      return httpError.message;

    return 'Failed to load GIFs from KLIPY.';
  }
}
