/*
 * ******************************************************************
 * Copyright (c) 2025 Broadcom. All Rights Reserved.
 * Broadcom Confidential. The term "Broadcom" refers to Broadcom Inc.
 * and/or its subsidiaries.
 * ******************************************************************
 */

import { Inject, Injectable, Optional } from "@angular/core";
import { CONTAINER_CREDENTIALS, Credentials } from "src/environments/access";
import { DecodedJwt } from "./decoded-jwt";
import { HttpClient, HttpHeaders } from "@angular/common/http";
import { Observable, tap, switchMap, map, EMPTY } from "rxjs";
import { CORE_UI_CUSTOM_EVENTS } from "ng-hyper-plugin-framework/src/public-api";

const OFFSET_SECONDS = 60;
/**
 * HTTP/HTTPS 'content-type' header.
 */
const CONTENT_TYPE_HEADER = "Content-Type";

@Injectable()
export class RefreshTokenService {
    private jwtToken: string;
    private decodedJwt: DecodedJwt;

    constructor(
        @Inject(CONTAINER_CREDENTIALS) @Optional() private credentials: Credentials,
        private http: HttpClient,
    ) {
        if (this.credentials) {
            this.jwtToken = this.credentials.jwt;

            try {
                this.decodedJwt = new DecodedJwt(this.jwtToken);
            } catch(e) {
                console.error(e);
            }
        }
    }

    public isExpired(): boolean {
        if (!this.credentials) {
            return false;
        }

        const exp = this.decodedJwt?.payload["exp"] ? this.decodedJwt?.payload["exp"] : 0;
        const current = Math.floor(Date.now() / 1000) + OFFSET_SECONDS;
        return current >= exp;
    }

    public refresh(): Observable<string> {
        if (!this.credentials) {
            return EMPTY;
        }

        const body = new URLSearchParams();
        body.set("grant_type", "refresh_token");

        const headers = this.getHeaders();

        return this.getRefreshToken(this.jwtToken)
            .pipe(
                tap((refreshToken) => body.set("refresh_token", refreshToken)),
                switchMap(() => this.http.post(this.getURL(), body.toString(), { headers })),
                map((tokenData: OAuthToken) => {
                    const jwt = tokenData.access_token;
                    this.setCredentialsJwt(this.jwtToken);
                    return jwt;
                })
            );
    }

    private getRefreshToken(accessToken: string): Observable<string> {
        const body = new URLSearchParams();
        body.set("grant_type", "urn:ietf:params:oauth:grant-type:jwt-bearer");
        body.set("assertion", accessToken);

        const headers = this.getHeaders();

        return this.http.post(this.getURL(), body.toString(), { headers }).pipe(
            map((tokenData: OAuthToken) => {
                return tokenData.refresh_token;
            })
        );
    }

    private getURL(): string {
        return "/oidc/oauth2/token";
    }

    private getHeaders(): HttpHeaders {
        return new HttpHeaders()
            .set(CONTENT_TYPE_HEADER, "application/x-www-form-urlencoded");
    }

    private setCredentialsJwt(jwt: string) {
        if (!this.credentials) {
            return;
        }
        this.jwtToken = jwt;
        this.credentials.jwt = jwt;
        try {
            this.decodedJwt = new DecodedJwt(this.jwtToken);
        } catch(e) {
            console.error(e);
        }
        localStorage.setItem("jwt", this.jwtToken);
        window.dispatchEvent(new CustomEvent(CORE_UI_CUSTOM_EVENTS.ON_JWT_CHANGE));
    }
}

/**
 * The response VMware Cloud Director returns to a successful request for an OAuth token.
 */
interface OAuthToken {
    /**
     * Access tokens are credentials used to access protected resources in VMware Cloud Director. An access token is a string representing an authorization issued to the client.
     */
    access_token?: string;

    /**
     * The type of the access token issued. The access token type provides the client with the information required to successfully utilize the access token to make a request to VMware Cloud Director.
     */
    token_type?: string;

    /**
     * The lifetime in seconds of the access token.
     */
    expires_in?: number;

    /**
     * Refresh tokens are credentials used to obtain access tokens. Refresh tokens are issued to the client by VMware Cloud Director and are used to obtain a new access token when the current access token becomes invalid or expires.
     */
    refresh_token?: string;

}