import { Axios } from 'axios';
import { consola } from 'consola/browser';
import type { Listing, User } from './models';
import { CreateListingRequestSchema, LoginRequestSchema, RegisterRequestSchema, type CreateListingRequest, type LoginRequest, type RegisterRequest } from './requests';

interface RequestError
{
    message: string;
}

class HeaptraderAPI
{
    private _axios: Axios;
    private _user?: User;
    private _lastUserUpdateTimeMs?: number;

    constructor(baseURL: string = "http://localhost")
    {
        this._axios = new Axios({
            baseURL,
            withCredentials: true,
            withXSRFToken: true,
        });
    }

    async csrfCookie()
    {
        await this._axios.get("/sanctum/csrf-cookie");
    }

    async login(options: LoginRequest): Promise<boolean>
    {
        const validated = LoginRequestSchema.safeParse(options);
        if (!validated.success) {
            consola.error("Login request was invalid: " + validated.error.message);
            return false;
        }
        await this.csrfCookie();
        const response = await this._axios.post<User | RequestError>("/login", validated.data);
        if (response.status === 200) {
            this._user = response.data as User;
            this._lastUserUpdateTimeMs = Date.now();
            consola.info("Logged in as " + this._user.name);
            return true;
        } else {
            consola.error("Failed to log in: " + (response.data as RequestError).message);
            return false;
        }
    }

    async register(options: RegisterRequest): Promise<boolean>
    {
        const validated = RegisterRequestSchema.safeParse(options);
        if (!validated.success) {
            consola.error("Register request was invalid: " + validated.error.message);
            return false;
        }
        await this.csrfCookie();
        const response = await this._axios.post<null | RequestError>("/register", validated.data);
        if (response.status === 201) {
            await this.refreshUser();
            consola.info("Registered as " + this._user?.name);
            return true;
        } else {
            consola.error("Failed to register: " + (response.data as RequestError).message);
            return false;
        }
    }

    async user(): Promise<User | null>
    {
        // TTL on cached user is 5 minutes
        if (!this._lastUserUpdateTimeMs || Date.now() - this._lastUserUpdateTimeMs > 5 * 60 * 1000) {
            await this.refreshUser();
            this._lastUserUpdateTimeMs = Date.now();
        }
        return this._user ?? null;
    }

    async refreshUser()
    {
        // Do up to one retry
        for (let i = 0; i < 2; i++)
        {
            const response = await this._axios.get<User | RequestError>("/api/user");
            if (response.status === 200) {
                this._user = response.data as User;
                return;
            } else if (response.status === 419) {
                consola.warn("Error 419 occurred while refreshing user info, fetching new CSRF cookie...");
                await this.csrfCookie();
            } else if (response.status === 403) {
                consola.error("Error 403 occurred while refreshing user info, we must be logged out.");
                this._user = undefined;
                return;
            } else {
                const error = response.data as RequestError;
                consola.error("Error " + response.status + " while refreshing user info: " + error.message);
            }
            // Wait 1 second for retry
            consola.info("Retrying get user request...");
            await new Promise(resolve => setTimeout(resolve, 1000));
        }
    }

    async heartbeat(): Promise<boolean>
    {
        const response = await this._axios.get("/up");
        return response.status === 200;
    }

    async createListing(options: CreateListingRequest): Promise<number | null>
    {
        const validated = CreateListingRequestSchema.safeParse(options);
        if (!validated.success) {
            consola.error("Create listing request was invalid: " + validated.error.message);
            return null;
        }
        const response = await this._axios.post<Listing | RequestError>("/api/listing", validated.data);
        if (response.status === 200) {
            return (response.data as Listing).id;
        } else if (response.status === 403) {
            consola.error("Create listing request failed: 403 Forbidden.");
            if (!this._user?.email_verified_at) {
                consola.error("Your email is not yet verified, so you cannot create listings.");
            }
            return null;
        } else {
            const error = response.data as RequestError;
            consola.error("Create listing request was rejected by server (" + response.status + "): " + error.message);
            return null;
        }
    }

    async getHomepageListings(): Promise<Array<Listing>>
    {
        // TODO: account for pagination and filtering options
        const response = await this._axios.get<Array<Listing>>("/api/listing");
        return response.data;
    }
}
