/*
 *   This file is a part of the SteamWar software.
 *
 *   Copyright (C) 2025 SteamWar.de-Serverteam
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Affero General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *   GNU Affero General Public License for more details.
 *
 *   You should have received a copy of the GNU Affero General Public License
 *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

import {readable, writable} from "svelte/store";
import {tokenStore} from "@repo/repo.ts";
import dayjs, {type Dayjs} from "dayjs";
import {type AuthToken, AuthTokenSchema} from "@type/auth.ts";

export class AuthV2Repo {
    private accessToken: string | undefined;
    private accessTokenExpires: Dayjs | undefined;
    private refreshToken: string | undefined;
    private refreshTokenExpires: Dayjs | undefined;

    constructor() {
        if (typeof localStorage === "undefined") {
            return;
        }

        this.accessToken = localStorage.getItem("sw-access-token") ?? undefined;
        if (this.accessToken) {
            this.accessTokenExpires = dayjs(localStorage.getItem("sw-access-token-expires") ?? "");
        }

        this.refreshToken = localStorage.getItem("sw-refresh-token") ?? undefined;
        if (this.refreshToken) {
            loggedIn.set(true);
            this.refreshTokenExpires = dayjs(localStorage.getItem("sw-refresh-token-expires") ?? "");
        }
    }

    async login(name: string, password: string) {
        if (this.accessToken !== undefined || this.refreshToken !== undefined) {
            throw new Error("Already logged in");
        }

        try {
            const login = await this.request("/auth/state", {
                method: "POST",
                body: JSON.stringify({
                    name,
                    password,
                    keepLoggedIn: true,
                }),
            }).then(value => value.json()).then(value => AuthTokenSchema.parse(value));

            this.setLoginState(login);

            return true;
        } catch (e) {
            return false;
        }
    }

    async resetPassword(name: string, password: string, token: string) {
        return await this.requestWithToken(token, "/auth/register", {
            method: "POST",
            body: JSON.stringify({
                name,
                password,
            }),
        });
    }

    async logout() {
        if (this.accessToken === undefined) {
            return;
        }

        await this.request("/auth/state", {
            method: "DELETE",
        });

        this.resetAccessToken();
        this.resetRefreshToken();
    }

    private setLoginState(tokens: AuthToken) {
        this.setAccessToken(tokens.accessToken.token, dayjs(tokens.accessToken.expires));
        this.setRefreshToken(tokens.refreshToken.token, dayjs(tokens.refreshToken.expires));
        loggedIn.set(true);
    }

    private setAccessToken(token: string, expires: Dayjs) {
        this.accessToken = token;
        this.accessTokenExpires = expires;
        localStorage.setItem("sw-access-token", token);
        localStorage.setItem("sw-access-token-expires", expires.toString());
    }

    private resetAccessToken() {
        if (this.accessToken === undefined) {
            return;
        }
        this.accessToken = undefined;
        this.accessTokenExpires = undefined;
        localStorage.removeItem("sw-access-token");
        localStorage.removeItem("sw-access-token-expires");
    }

    private setRefreshToken(token: string, expires: Dayjs) {
        this.refreshToken = token;
        this.refreshTokenExpires = expires;
        localStorage.setItem("sw-refresh-token", token);
        localStorage.setItem("sw-refresh-token-expires", expires.toString());
    }

    private resetRefreshToken() {
        if (this.refreshToken === undefined) {
            return;
        }
        this.refreshToken = undefined;
        this.refreshTokenExpires = undefined;
        localStorage.removeItem("sw-refresh-token");
        localStorage.removeItem("sw-refresh-token-expires");

        loggedIn.set(false);
    }

    private async refresh() {
        if (this.refreshToken === undefined || this.refreshTokenExpires === undefined || this.refreshTokenExpires.isBefore(dayjs().add(10, "seconds"))) {
            this.resetRefreshToken();
            this.resetAccessToken();

            return;
        }

        try {
            const response = await this.requestWithToken(this.refreshToken!, "/auth/state", {
                method: "PUT",
            }).then(value => value.json()).then(value => AuthTokenSchema.parse(value));

            this.setLoginState(response);
        } catch (e) {
            this.resetRefreshToken();
            this.resetAccessToken();
            return;
        }
    }

    async request(url: string, params: RequestInit = {}) {
        if (this.accessToken !== undefined && this.accessTokenExpires !== undefined && this.accessTokenExpires.isBefore(dayjs().add(10, "seconds"))) {
            await this.refresh();
        }

        return this.requestWithToken(this.accessToken ?? "", url, params);
    }

    private async requestWithToken(token: string, url: string, params: RequestInit = {}) {
        return fetch(`${import.meta.env.PUBLIC_API_SERVER}${url}`, {...params,
            headers: {
                ...(token !== "" ? {"Authorization": "Bearer " + (token)} : {}),
                "Content-Type": "application/json", ...params.headers,
            },
        })
            .then(value => {
                if (value.status === 401) {
                    tokenStore.set("");
                }
                return value;
            });
    }
}

export const loggedIn = writable(false);

export const authV2Repo = readable(new AuthV2Repo());