import { version } from "../constants";
import { LdbClientV2 } from "./LdbClientV2";
import { LocationReferenceClientV2 } from "./LocationReferenceClientV2";
import { MiscClientV2 } from "./MiscClientV2";
import { PisClientV2 } from "./PisClientV2";
import { TrainClientV2 } from "./TrainClientV2";
import { UserClientV2 } from "./UserClientV2";

import { ApiError, NetworkError } from "../errors";

export class BaseOwlBoardClient {
    protected baseUrl: string;
    protected apiKey: string;
    protected headers: Record<string, string>;

    constructor(baseUrl: string, apiKey: string) {
        this.baseUrl = baseUrl;
        this.apiKey = apiKey;
        this.headers = {
            "Content-Type": "application/json",
            "Accept": "application/json",
            "User-Agent": `OwlBoardTS/v${version}`,
            "uuid": this.apiKey,
        };
    }

    async makeRequest<T>(method: string, path: string, body?: any): Promise<T> {
        const url = `${this.baseUrl}${path}`;
        const options: RequestInit = {
            method,
            headers: this.headers,
            body: body ? JSON.stringify(body) : undefined,
        };

        let attempt = 0;
        const retries = 3;
        const delay = 500;
        const retryableStatusCodes = [408, 500, 502, 503, 504];

        while (attempt < retries) {
            try {
                const response = await fetch(url, options);
                if (!response.ok) {
                    const responseBody = await response.text();
                    const apiError = new ApiError("API Request Failed", response.status, responseBody);
                    if (retryableStatusCodes.includes(response.status)) {
                        attempt++;
                        if (attempt >= retries) { // Max attempts reached
                            throw apiError;
                        }
                        // Wait before retrying
                        await new Promise(res => setTimeout(res, delay));
                        continue; // Retry
                    } else { // Non-retryable status codes
                        throw apiError;
                    }
                }

                return (await response.json()) as T;
            } catch (error) {
                if (error instanceof TypeError) { // Indicates network errors with fetch
                    if (attempt < retries) {
                        attempt ++;
                        await new Promise(res => setTimeout(res, delay));
                        continue;
                    }
                    // Throw network error if max tries reached handling TypeError
                    throw new NetworkError("Network request failed");
                };

                throw error; // Rethrow any other error
            };
        }
        // This code should never be reached.
        throw new Error("Unexpected error after multiple retries.")
    }
}

export class OwlBoardClientV2 extends BaseOwlBoardClient {
    pis: PisClientV2;
    train: TrainClientV2;
    user: UserClientV2;
    locationReference: LocationReferenceClientV2;
    misc: MiscClientV2;
    ldb: LdbClientV2;

    constructor(baseUrl: string, apiKey: string) {
        super(baseUrl, apiKey);
        this.pis = new PisClientV2(this);
        this.train = new TrainClientV2(this);
        this.user = new UserClientV2(this);
        this.locationReference = new LocationReferenceClientV2(this);
        this.misc = new MiscClientV2(this);
        this.ldb = new LdbClientV2(this);
    }
}