// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import * as Root from '../root/root.js';

import * as DispatchHttpRequestClient from './DispatchHttpRequestClient.js';
import type {DispatchHttpRequestRequest} from './InspectorFrontendHostAPI.js';

export enum SubscriptionStatus {
  ENABLED = 'SUBSCRIPTION_STATE_ENABLED',
  PENDING = 'SUBSCRIPTION_STATE_PENDING',
  CANCELED = 'SUBSCRIPTION_STATE_CANCELED',
  REFUNDED = 'SUBSCRIPTION_STATE_REFUNDED',
  AWAITING_FIX = 'SUBSCRIPTION_STATE_AWAITING_FIX',
  ON_HOLD = 'SUBSCRIPTION_STATE_ACCOUNT_ON_HOLD',
}

export enum SubscriptionTier {
  PREMIUM_ANNUAL = 'SUBSCRIPTION_TIER_PREMIUM_ANNUAL',
  PREMIUM_MONTHLY = 'SUBSCRIPTION_TIER_PREMIUM_MONTHLY',
  PRO_ANNUAL = 'SUBSCRIPTION_TIER_PRO_ANNUAL',
  PRO_MONTHLY = 'SUBSCRIPTION_TIER_PRO_MONTHLY',
}

export enum EligibilityStatus {
  ELIGIBLE = 'ELIGIBLE',
  NOT_ELIGIBLE = 'NOT_ELIGIBLE',
}

export enum EmailPreference {
  ENABLED = 'ENABLED',
  DISABLED = 'DISABLED',
}

interface CheckElibigilityResponse {
  createProfile: EligibilityStatus;
}

interface BatchGetAwardsResponse {
  awards?: Award[];
}

export interface Award {
  name: string;
  badge: {
    title: string,
    description: string,
    imageUri: string,
    deletableByUser: boolean,
  };
  title: string;
  description: string;
  imageUri: string;
  createTime: string;
  awardingUri: string;
}

export interface Profile {
  // Resource name of the profile.
  // Format: profiles/{obfuscated_profile_id}
  name: string;
  activeSubscription?: {
    subscriptionStatus: SubscriptionStatus,
    // To ensure forward compatibility, we accept any string, allowing the server to
    // introduce new subscription tiers without breaking older clients.
    subscriptionTier: SubscriptionTier|string,
  };
}

export interface GetProfileResponse {
  profile: Profile|null;
  isEligible: boolean;
}

/**
 * The `batchGet` awards endpoint returns badge names with an
 * obfuscated user ID (e.g., `profiles/12345/awards/badge-name`).
 * This function normalizes them to use `me` instead of the ID
 * (e.g., `profiles/me/awards/badge-path`) to match the format
 * used for client-side requests.
 **/
function normalizeBadgeName(name: string): string {
  return name.replace(/profiles\/[^/]+\/awards\//, 'profiles/me/awards/');
}

export const GOOGLE_DEVELOPER_PROGRAM_PROFILE_LINK = 'https://developers.google.com/profile/u/me';
const ORIGIN_APPLICATION_NAME = 'APPLICATION_CHROME_DEVTOOLS';

async function makeHttpRequest<R>(request: DispatchHttpRequestRequest): Promise<R> {
  if (!isGdpProfilesAvailable()) {
    throw new DispatchHttpRequestClient.DispatchHttpRequestError(
        DispatchHttpRequestClient.ErrorType.HTTP_RESPONSE_UNAVAILABLE);
  }

  const response = await DispatchHttpRequestClient.makeHttpRequest(request) as R;
  return response;
}

const SERVICE_NAME = 'gdpService';
let gdpClientInstance: GdpClient|null = null;
export class GdpClient {
  #cachedProfilePromise?: Promise<Profile>;
  #cachedEligibilityPromise?: Promise<CheckElibigilityResponse>;

  private constructor() {
  }

  static instance({forceNew}: {
    forceNew: boolean,
  } = {forceNew: false}): GdpClient {
    if (!gdpClientInstance || forceNew) {
      gdpClientInstance = new GdpClient();
    }
    return gdpClientInstance;
  }

  /**
   * Fetches the user's GDP profile and eligibility status.
   *
   * It first attempts to fetch the profile. If the profile is not found
   * (a `NOT_FOUND` error), this is handled gracefully by treating the profile
   * as `null` and then proceeding to check for eligibility.
   *
   * @returns A promise that resolves with an object containing the `profile`
   * and `isEligible` status, or `null` if an unexpected error occurs.
   */
  async getProfile(): Promise<GetProfileResponse|null> {
    try {
      const profile = await this.#getProfile();
      return {
        profile,
        isEligible: true,
      };
    } catch (err: unknown) {
      if (err instanceof DispatchHttpRequestClient.DispatchHttpRequestError &&
          err.type === DispatchHttpRequestClient.ErrorType.HTTP_RESPONSE_UNAVAILABLE) {
        return null;
      }
    }

    try {
      const checkEligibilityResponse = await this.#checkEligibility();
      return {
        profile: null,
        isEligible: checkEligibilityResponse.createProfile === EligibilityStatus.ELIGIBLE,
      };
    } catch {
      return null;
    }
  }

  async #getProfile(): Promise<Profile> {
    if (this.#cachedProfilePromise) {
      return await this.#cachedProfilePromise;
    }

    this.#cachedProfilePromise = makeHttpRequest<Profile>({
                                   service: SERVICE_NAME,
                                   path: '/v1beta1/profile:get',
                                   method: 'GET',
                                 }).then(profile => {
      this.#cachedEligibilityPromise = Promise.resolve({createProfile: EligibilityStatus.ELIGIBLE});
      return profile;
    });

    return await this.#cachedProfilePromise;
  }

  async #checkEligibility(): Promise<CheckElibigilityResponse> {
    if (this.#cachedEligibilityPromise) {
      return await this.#cachedEligibilityPromise;
    }

    this.#cachedEligibilityPromise =
        makeHttpRequest({service: SERVICE_NAME, path: '/v1beta1/eligibility:check', method: 'GET'});

    return await this.#cachedEligibilityPromise;
  }

  /**
   * @returns null if the request fails, the awarded badge names otherwise.
   */
  async getAwardedBadgeNames({names}: {names: string[]}): Promise<Set<string>|null> {
    try {
      const response = await makeHttpRequest<BatchGetAwardsResponse>({
        service: SERVICE_NAME,
        path: '/v1beta1/profiles/me/awards:batchGet',
        method: 'GET',
        queryParams: {
          allowMissing: 'true',
          names,
        }
      });

      return new Set(response.awards?.map(award => normalizeBadgeName(award.name)) ?? []);
    } catch {
      return null;
    }
  }

  async createProfile({user, emailPreference}: {user: string, emailPreference: EmailPreference}):
      Promise<Profile|null> {
    try {
      const response = await makeHttpRequest<Profile>({
        service: SERVICE_NAME,
        path: '/v1beta1/profiles',
        method: 'POST',
        body: JSON.stringify({
          user,
          newsletter_email: emailPreference,
          creation_origin: {
            origin_application: ORIGIN_APPLICATION_NAME,
          }
        }),
      });
      this.#clearCache();
      return response;
    } catch {
      return null;
    }
  }

  #clearCache(): void {
    this.#cachedProfilePromise = undefined;
    this.#cachedEligibilityPromise = undefined;
  }

  async createAward({name}: {name: string}): Promise<Award|null> {
    try {
      const response = await makeHttpRequest<Award>({
        service: SERVICE_NAME,
        path: '/v1beta1/profiles/me/awards',
        method: 'POST',
        body: JSON.stringify({
          awardingUri: 'devtools://devtools',
          name,
        })
      });
      return response;
    } catch {
      return null;
    }
  }
}

export function isGdpProfilesAvailable(): boolean {
  const isBaseFeatureEnabled = Boolean(Root.Runtime.hostConfig.devToolsGdpProfiles?.enabled);
  const isBrandedBuild = Boolean(Root.Runtime.hostConfig.devToolsGdpProfilesAvailability?.enabled);
  const isOffTheRecordProfile = Root.Runtime.hostConfig.isOffTheRecord;
  const isDisabledByEnterprisePolicy =
      getGdpProfilesEnterprisePolicy() === Root.Runtime.GdpProfilesEnterprisePolicyValue.DISABLED;
  return isBaseFeatureEnabled && isBrandedBuild && !isOffTheRecordProfile && !isDisabledByEnterprisePolicy;
}

export function getGdpProfilesEnterprisePolicy(): Root.Runtime.GdpProfilesEnterprisePolicyValue {
  return (
      Root.Runtime.hostConfig.devToolsGdpProfilesAvailability?.enterprisePolicyValue ??
      Root.Runtime.GdpProfilesEnterprisePolicyValue.DISABLED);
}

export function isBadgesEnabled(): boolean {
  const isBadgesEnabledByEnterprisePolicy =
      getGdpProfilesEnterprisePolicy() === Root.Runtime.GdpProfilesEnterprisePolicyValue.ENABLED;
  const isBadgesEnabledByFeatureFlag = Boolean(Root.Runtime.hostConfig.devToolsGdpProfiles?.badgesEnabled);
  return isBadgesEnabledByEnterprisePolicy && isBadgesEnabledByFeatureFlag;
}

export function isStarterBadgeEnabled(): boolean {
  return Boolean(Root.Runtime.hostConfig.devToolsGdpProfiles?.starterBadgeEnabled);
}
