import { Injectable, OnDestroy, signal } from '@angular/core';
import { firstValueFrom, forkJoin } from 'rxjs';
import {
  CharacterResponse,
  CurrentLocationConnection,
  CurrentLocationResponse,
  CurrentTravel,
} from '../../core/api/game-api.models';
import { GameApiService } from '../../core/api/game-api.service';

@Injectable({ providedIn: 'root' })
export class WorldStore implements OnDestroy {
  private readonly characterState = signal<CharacterResponse | null>(null);
  private readonly currentLocationState = signal<CurrentLocationResponse | null>(null);
  private readonly selectedConnectionState = signal<CurrentLocationConnection | null>(null);
  private readonly currentTravelState = signal<CurrentTravel | null>(null);
  private readonly remainingSecondsState = signal<number | null>(null);
  private readonly loadingState = signal(false);
  private readonly errorState = signal<string | null>(null);
  private countdownTimer: ReturnType<typeof setInterval> | undefined;
  private travelRetryTimer: ReturnType<typeof setTimeout> | undefined;
  private travelPollInFlight = false;
  private destroyed = false;

  readonly character = this.characterState.asReadonly();
  readonly currentLocation = this.currentLocationState.asReadonly();
  readonly selectedConnection = this.selectedConnectionState.asReadonly();
  readonly currentTravel = this.currentTravelState.asReadonly();
  readonly remainingSeconds = this.remainingSecondsState.asReadonly();
  readonly loading = this.loadingState.asReadonly();
  readonly error = this.errorState.asReadonly();

  constructor(private readonly api: GameApiService) {}

  async load(): Promise<void> {
    if (this.destroyed) {
      return;
    }

    this.loadingState.set(true);
    this.errorState.set(null);

    try {
      const { character, location, travel } = await this.loadSnapshot();
      if (this.destroyed) {
        return;
      }

      this.characterState.set(character);
      this.currentLocationState.set(location);
      this.selectedConnectionState.set(null);
      await this.setCurrentTravel(travel);
    } catch (error) {
      if (!this.destroyed) {
        this.errorState.set(this.toErrorMessage(error));
      }
    } finally {
      if (!this.destroyed) {
        this.loadingState.set(false);
      }
    }
  }

  selectConnection(connection: CurrentLocationConnection | null): void {
    this.selectedConnectionState.set(connection);
  }

  async startTravel(): Promise<void> {
    if (this.destroyed) {
      return;
    }

    const connection = this.selectedConnectionState();
    if (!connection) {
      return;
    }

    this.loadingState.set(true);
    this.errorState.set(null);

    try {
      const travel = await firstValueFrom(this.api.startTravel(connection.targetLocation.id));
      if (this.destroyed) {
        return;
      }

      await this.setCurrentTravel(travel);
    } catch (error) {
      if (!this.destroyed) {
        this.errorState.set(this.toErrorMessage(error));
      }
    } finally {
      if (!this.destroyed) {
        this.loadingState.set(false);
      }
    }
  }

  ngOnDestroy(): void {
    this.destroyed = true;
    this.stopCountdown();
    this.clearTravelRetry();
  }

  private loadSnapshot(): Promise<{
    character: CharacterResponse;
    location: CurrentLocationResponse;
    travel: CurrentTravel;
  }> {
    return firstValueFrom(
      forkJoin({
        character: this.api.getCharacter(),
        location: this.api.getCurrentLocation(),
        travel: this.api.getCurrentTravel(),
      }),
    );
  }

  private async setCurrentTravel(travel: CurrentTravel): Promise<void> {
    if (this.destroyed) {
      return;
    }

    this.currentTravelState.set(travel);
    this.stopCountdown();
    this.clearTravelRetry();

    if (travel.status === 'TRAVELLING') {
      this.startCountdown(travel.arrivesAt);
      return;
    }

    this.remainingSecondsState.set(null);
    if (travel.status === 'COMPLETED') {
      this.selectedConnectionState.set(null);
      this.loadingState.set(true);
      try {
        await this.reloadAuthoritativeState();
        if (!this.destroyed) {
          this.currentTravelState.set({ status: 'IDLE' });
        }
      } finally {
        if (!this.destroyed) {
          this.loadingState.set(false);
        }
      }
    }
  }

  private startCountdown(arrivesAt: string): void {
    const updateRemainingSeconds = () => {
      if (this.destroyed) {
        return;
      }

      const remainingSeconds = Math.max(0, Math.ceil((Date.parse(arrivesAt) - Date.now()) / 1000));
      this.remainingSecondsState.set(remainingSeconds);

      if (remainingSeconds === 0) {
        this.stopCountdown();
        this.pollCurrentTravel();
      }
    };

    updateRemainingSeconds();
    if (this.countdownTimer === undefined && this.remainingSecondsState() !== 0) {
      this.countdownTimer = setInterval(updateRemainingSeconds, 1_000);
    }
  }

  private pollCurrentTravel(): void {
    if (this.destroyed || this.travelPollInFlight) {
      return;
    }

    this.travelPollInFlight = true;
    void this.refreshCurrentTravel();
  }

  private async refreshCurrentTravel(): Promise<void> {
    try {
      const travel = await firstValueFrom(this.api.getCurrentTravel());
      if (this.destroyed) {
        return;
      }

      this.errorState.set(null);
      await this.setCurrentTravel(travel);
      if (travel.status === 'TRAVELLING') {
        this.scheduleTravelRetry();
      }
    } catch (error) {
      if (!this.destroyed) {
        this.errorState.set(this.toErrorMessage(error));
        this.scheduleTravelRetry();
      }
    } finally {
      this.travelPollInFlight = false;
    }
  }

  private async reloadAuthoritativeState(): Promise<void> {
    const { character, location } = await firstValueFrom(
      forkJoin({
        character: this.api.getCharacter(),
        location: this.api.getCurrentLocation(),
      }),
    );
    if (this.destroyed) {
      return;
    }

    this.characterState.set(character);
    this.currentLocationState.set(location);
    this.selectedConnectionState.set(null);
  }

  private stopCountdown(): void {
    if (this.countdownTimer !== undefined) {
      clearInterval(this.countdownTimer);
      this.countdownTimer = undefined;
    }
  }

  private scheduleTravelRetry(): void {
    if (this.destroyed || this.travelRetryTimer !== undefined) {
      return;
    }

    this.travelRetryTimer = setTimeout(() => {
      this.travelRetryTimer = undefined;
      this.pollCurrentTravel();
    }, 1_000);
  }

  private clearTravelRetry(): void {
    if (this.travelRetryTimer !== undefined) {
      clearTimeout(this.travelRetryTimer);
      this.travelRetryTimer = undefined;
    }
  }

  private toErrorMessage(error: unknown): string {
    return error instanceof Error ? error.message : 'Unable to load world state.';
  }
}
