import axios, { AxiosRequestConfig } from 'axios';
import https from 'https';
import { makeHeadersWithCookie } from '../utils';
import { getAppConfigsFromFS } from "../controllers/workspace";
import { getOpenApp } from '../database/cliconfig';

/**
 * Create an axios instance with appropriate SSL configuration based on environment
 * @param config Optional axios request configuration
 * @returns Configured axios instance
 */
function createAxiosInstance(config: AxiosRequestConfig = {}) {
  // Only disable certificate verification in non-production environments
  if (process.env.NODE_ENV !== 'production') {
    const httpsAgent = new https.Agent({
      rejectUnauthorized: false // Ignore certificate verification errors
    });
    return axios.create({ ...config, httpsAgent });
  }

  return axios.create(config);
}

/**
 * Interface for token usage request
 */
export interface TokenUsageRequest {
  appId: string;
  userId: string;
  inputTokens: number;
  outputTokens: number;
  llmModel?: string;
  cliChatId?: string;
}

/**
 * Interface for token allocation request
 */
export interface TokenAllocationRequest {
  organizationId: string;
  tokenCount: number;
  source: TokenSource;
  daysToExpire?: number;
  notes?: string;
}

/**
 * Enum for token sources
 */
export enum TokenSource {
  PURCHASE = 'PURCHASE',
  PROMOTION = 'PROMOTION',
  FREE_TIER = 'FREE_TIER',
  REFUND = 'REFUND',
  ADMIN = 'ADMIN'
}

/**
 * Interface for token usage response
 */
export interface TokenUsageResponse {
  id: string;
  appId: string;
  userId: string;
  inputTokens: number;
  outputTokens: number;
  totalTokens: number;
  llmModel: string;
  createdAt: string;
  updatedAt: string;
}

/**
 * Interface for token allocation response
 */
export interface TokenAllocationResponse {
  id: string;
  organizationId: string;
  tokenCount: number;
  source: TokenSource;
  expiresAt: string | null;
  notes: string | null;
  createdAt: string;
  updatedAt: string;
}

/**
 * Interface for available tokens response
 */
export interface AvailableTokensResponse {
  availableTokens: number;
}

/**
 * Interface for allocated tokens response
 */
export interface AllocatedTokensResponse {
  allocatedTokens: number;
}

/**
 * Interface for token stats response
 */
export interface TokenStatsResponse {
  totalAllocated: number;
  totalUsed: number;
  availableTokens: number;
  usageByApp: {
    appId: string;
    appName: string;
    totalTokens: number;
    inputTokens: number;
    outputTokens: number;
  }[];
  usageByModel: {
    model: string;
    totalTokens: number;
    inputTokens: number;
    outputTokens: number;
  }[];
  usageByDay: {
    date: string;
    totalTokens: number;
    inputTokens: number;
    outputTokens: number;
  }[];
}

/**
 * Get the base URL for token API calls
 * @param apptileBackendUrl The backend URL from app config
 * @returns The base URL for token API calls
 */
function getTokenApiBaseUrl(apptileBackendUrl: string): string {
  return process.env.NODE_ENV === "production" ? `${apptileBackendUrl}/admin/api/tokens` : `${apptileBackendUrl}/api/tokens`;
}

/**
 * Record token usage for an app
 * @param tokenUsageBody The token usage data
 * @param appId The app ID
 * @returns Promise with the token usage response
 */
export async function recordTokenUsage(
  tokenUsageBody: TokenUsageRequest,
  appId: string
): Promise<TokenUsageResponse | undefined> {
  try {
    const openedApp = await getOpenApp(appId);
    if (!openedApp) {
      throw new Error(`App with ID ${appId} not found or not open`);
    }

    const apptileConfig = await getAppConfigsFromFS(appId);
    const headers = makeHeadersWithCookie(openedApp.apptilecookie, {
      'Content-Type': 'application/json'
    });

    const baseUrl = getTokenApiBaseUrl(apptileConfig.APPTILE_BACKEND_URL);
    const response = await createAxiosInstance().post(
      `${baseUrl}/usage`,
      tokenUsageBody,
      headers
    );

    if (response.status >= 400) {
      throw new Error(`Failed to record token usage: ${response.data}`);
    }

    console.info(`Token usage recorded successfully for app ${appId}`);
    return response.data;
  } catch (error: any) {
    console.info(`Error recording token usage: ${error.message}`);
    return undefined;
  }
}

/**
 * Add tokens to an organization
 * @param tokenAllocation The token allocation data
 * @param appId The app ID (used to get authentication)
 * @returns Promise with the token allocation response
 */
export async function allocateTokens(
  tokenAllocation: TokenAllocationRequest,
  appId: string
): Promise<TokenAllocationResponse | undefined> {
  try {
    const openedApp = await getOpenApp(appId);
    if (!openedApp) {
      throw new Error(`App with ID ${appId} not found or not open`);
    }

    const apptileConfig = await getAppConfigsFromFS(appId);
    const headers = makeHeadersWithCookie(openedApp.apptilecookie, {
      'Content-Type': 'application/json'
    });

    const baseUrl = getTokenApiBaseUrl(apptileConfig.APPTILE_BACKEND_URL);
    const response = await createAxiosInstance().post(
      `${baseUrl}/allocate`,
      tokenAllocation,
      headers
    );

    if (response.status >= 400) {
      throw new Error(`Failed to allocate tokens: ${response.data}`);
    }

    console.info(`Tokens allocated successfully for organization ${tokenAllocation.organizationId}`);
    return response.data;
  } catch (error: any) {
    console.info(`Error allocating tokens: ${error.message}`);
    return undefined;
  }
}

/**
 * Get available tokens for an organization
 * @param organizationId The organization ID
 * @param appId The app ID (used to get authentication)
 * @returns Promise with the available tokens response
 */
export async function getAvailableTokens(
  organizationId: string,
  appId: string
): Promise<AvailableTokensResponse| undefined> {
  try {
    const openedApp = await getOpenApp(appId);
    if (!openedApp) {
      throw new Error(`App with ID ${appId} not found or not open`);
    }

    const apptileConfig = await getAppConfigsFromFS(appId);
    const headers = makeHeadersWithCookie(openedApp.apptilecookie, {});

    const baseUrl = getTokenApiBaseUrl(apptileConfig.APPTILE_BACKEND_URL);
    const response = await createAxiosInstance().get(
      `${baseUrl}/available/${organizationId}`,
      headers
    );

    if (response.status >= 400) {
      throw new Error(`Failed to get available tokens: ${response.data}`);
    }

    return response.data;
  } catch (error: any) {
    console.info(`Error getting available tokens: ${error.message}`);
    return undefined;
  }
}

/**
 * Get allocated tokens for an organization
 * @param organizationId The organization ID
 * @param appId The app ID (used to get authentication)
 * @returns Promise with the allocated tokens response
 */
export async function getAllocatedTokens(
  organizationId: string,
  appId: string
): Promise<AllocatedTokensResponse | undefined> {
  try {
    const openedApp = await getOpenApp(appId);
    if (!openedApp) {
      throw new Error(`App with ID ${appId} not found or not open`);
    }

    const apptileConfig = await getAppConfigsFromFS(appId);
    const headers = makeHeadersWithCookie(openedApp.apptilecookie, {});

    const baseUrl = getTokenApiBaseUrl(apptileConfig.APPTILE_BACKEND_URL);
    const response = await createAxiosInstance().get(
      `${baseUrl}/allocated/${organizationId}`,
      headers
    );

    if (response.status >= 400) {
      throw new Error(`Failed to get allocated tokens: ${response.data}`);
    }

    return response.data;
  } catch (error: any) {
    console.info(`Error getting allocated tokens: ${error.message}`);
    return undefined;
  }
}

/**
 * Get token usage statistics for an organization
 * @param organizationId The organization ID
 * @param appId The app ID (used to get authentication)
 * @returns Promise with the token stats response
 */
export async function getTokenStats(
  organizationId: string,
  appId: string
): Promise<TokenStatsResponse| undefined> {
  try {
    const openedApp = await getOpenApp(appId);
    if (!openedApp) {
      throw new Error(`App with ID ${appId} not found or not open`);
    }

    const apptileConfig = await getAppConfigsFromFS(appId);
    const headers = makeHeadersWithCookie(openedApp.apptilecookie, {});

    const baseUrl = getTokenApiBaseUrl(apptileConfig.APPTILE_BACKEND_URL);
    const response = await createAxiosInstance().get(
      `${baseUrl}/stats/${organizationId}`,
      headers
    );

    if (response.status >= 400) {
      throw new Error(`Failed to get token stats: ${response.data}`);
    }

    return response.data;
  } catch (error: any) {
    console.info(`Error getting token stats: ${error.message}`);
    return undefined;
  }
}
