import { NetworkError } from '../../utils/GameErrors';
import NullNetwork from './NullNetwork';

interface ReplayResponse
{
    max_step_id?: number;
    step_id?: number;
    [key: string]: unknown;
}

/** Network service that replays deterministic responses from a static endpoint. */
export default class ReplayNetwork extends NullNetwork
{
    public override name = 'ReplayNetwork';

    private readonly replayUrl: string;
    private maxStepId: number | null = null;
    private stepId: number | null = null;

    constructor()
    {
        super();
        const params = new URLSearchParams(window.location.search);

        this._token = params.get('play_token') ?? '';
        this._serverUrl = params.get('server_url') ?? '';
        this.replayUrl = params.get('replayUrl') ?? '';
    }

    public override get baseAssetsUrl(): string
    {
        return __OPTIONS__.resources_path;
    }

    public override get baseImgsUrl(): string
    {
        return this.baseAssetsUrl;
    }

    public override get baseUrl(): string
    {
        return __OPTIONS__.api;
    }

    public override get pathInit(): string
    {
        return 'init.json';
    }

    public override get pathBet(): string
    {
        return '';
    }

    /** Bootstraps the replay session with the initial payload. */
    public override async init(): Promise<ReplayResponse | NetworkError>
    {
        return this.executeWithReplayUrl(async (url) => this.createReplayRequest(url, this.createReplayRequestInit()));
    }

    /** Refreshes replay state while keeping the cached sequence. */
    public override async heartBeat(): Promise<ReplayResponse | NetworkError>
    {
        return this.executeWithReplayUrl(async (url) => this.createReplayRequest(url, this.createReplayRequestInit()));
    }

    /** Returns the next recorded bet response. */
    public override async bet(): Promise<ReplayResponse | NetworkError>
    {
        return this.executeWithReplayUrl(async (url) => this.createReplayRequest(`${url}/${this.getNextStepId()}`));
    }

    public override async freespin(): Promise<ReplayResponse | NetworkError>
    {
        return this.bet();
    }

    public override async respin(): Promise<ReplayResponse | NetworkError>
    {
        return this.bet();
    }

    public override async buyFreespins(): Promise<ReplayResponse | NetworkError>
    {
        return this.bet();
    }

    public override async buyBonus(): Promise<ReplayResponse | NetworkError>
    {
        return this.bet();
    }

    public override async wildSpin(): Promise<ReplayResponse | NetworkError>
    {
        return this.bet();
    }

    private createReplayRequestInit(): RequestInit
    {
        return {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
            },
        };
    }

    private async createReplayRequest(url: string, requestInit?: RequestInit): Promise<ReplayResponse | NetworkError>
    {
        try
        {
            const response = await fetch(url, { ...requestInit, method: 'GET' });

            if (!response.ok)
            {
                throw new Error(`HTTP error! status: ${response.status}`);
            }

            const data = await response.json() as ReplayResponse;

            if (typeof data.max_step_id === 'number')
            {
                this.maxStepId = data.max_step_id;
            }

            if (typeof data.step_id === 'number')
            {
                this.stepId = data.step_id;
            }

            return data;
        }
        catch (error)
        {
            console.error('createReplayRequest error:', error);

            return new NetworkError();
        }
    }

    private getNextStepId(): number
    {
        if (this.stepId !== null && this.maxStepId !== null && this.stepId < this.maxStepId)
        {
            return this.stepId + 1;
        }

        return 1;
    }

    private ensureReplayUrl(): string
    {
        if (!this.replayUrl)
        {
            throw new NetworkError();
        }

        return this.replayUrl;
    }

    private async executeWithReplayUrl(callback: (url: string) => Promise<ReplayResponse | NetworkError>): Promise<ReplayResponse | NetworkError>
    {
        try
        {
            const url = this.ensureReplayUrl();

            return await callback(url);
        }
        catch (error)
        {
            console.error('ReplayNetwork URL error:', error);

            return error instanceof NetworkError ? error : new NetworkError();
        }
    }
}
