/**
 * Copyright (c) Microsoft Corporation. All rights reserved.
 * Licensed under the MIT License.
 */

import { AgentType } from './agentType'
import { ConnectionSettings } from './connectionSettings'
import { debug } from '@microsoft/agents-telemetry'
import { PowerPlatformCloud } from './powerPlatformCloud'
import { PrebuiltBotStrategy } from './strategies/prebuiltBotStrategy'
import { PublishedBotStrategy } from './strategies/publishedBotStrategy'

const logger = debug('copilot-studio:power-platform')

/**
 * Generates the connection URL for Copilot Studio.
 * @param settings - The connection settings.
 * @param conversationId - Optional conversation ID.
 * @returns The connection URL.
 * @throws Will throw an error if required settings are missing or invalid.
 */
export function getCopilotStudioConnectionUrl (
  settings: ConnectionSettings,
  conversationId?: string
): string {
  const schemaName = settings.schemaName?.trim() || settings.agentIdentifier?.trim()
  if (!settings.directConnectUrl?.trim() && (!settings.environmentId?.trim() || !schemaName?.trim())) {
    throw new Error('Either directConnectUrl OR both environmentId and schemaName/agentIdentifier must be provided')
  }

  if (settings.directConnectUrl?.trim()) {
    logger.debug(`Using direct connection: ${settings.directConnectUrl}`)
    if (!isValidUri(settings.directConnectUrl)) {
      throw new Error('directConnectUrl must be a valid URL')
    }

    return createURL(settings.directConnectUrl, conversationId).href
  }

  const cloudSetting = settings.cloud ?? PowerPlatformCloud.Prod
  const agentType = settings.copilotAgentType ?? AgentType.Published

  logger.debug(`Using cloud setting: ${cloudSetting}`)
  logger.debug(`Using agent type: ${agentType}`)

  if (cloudSetting === PowerPlatformCloud.Other) {
    if (!settings.customPowerPlatformCloud?.trim()) {
      throw new Error('customPowerPlatformCloud must be provided when PowerPlatformCloud is Other')
    } else if (isValidUri(settings.customPowerPlatformCloud)) {
      logger.debug(`Using custom Power Platform cloud: ${settings.customPowerPlatformCloud}`)
    } else {
      throw new Error(
        'customPowerPlatformCloud must be a valid URL'
      )
    }
  }

  const host = getEnvironmentEndpoint(cloudSetting, settings.environmentId!, settings.customPowerPlatformCloud)

  const strategy = {
    [AgentType.Published]: () => new PublishedBotStrategy({
      host,
      schema: schemaName!,
    }),
    [AgentType.Prebuilt]: () => new PrebuiltBotStrategy({
      host,
      schema: schemaName!,
    }),
  }[agentType]()

  const url = strategy.getConversationUrl(conversationId)
  logger.debug(`Generated Copilot Studio connection URL: ${url}`)
  return url
}

/**
 * Generates the subscribe URL for Copilot Studio Server-Sent Events (SSE).
 * @param settings - The connection settings.
 * @param conversationId - The conversation ID to subscribe to.
 * @returns The subscribe URL.
 * @throws Will throw an error if required settings are missing or invalid.
 */
export function getCopilotStudioSubscribeUrl (
  settings: ConnectionSettings,
  conversationId: string
): string {
  if (!conversationId || !conversationId.trim()) {
    throw new Error('conversationId is required for subscribe URL')
  }

  const baseUrl = getCopilotStudioConnectionUrl(settings, conversationId)
  const url = new URL(baseUrl)
  url.pathname = url.pathname.endsWith('/')
    ? `${url.pathname}subscribe`
    : `${url.pathname}/subscribe`
  const subscribeUrl = url.href

  logger.debug(`Generated Copilot Studio subscribe URL: ${subscribeUrl}`)
  return subscribeUrl
}

/**
 * Returns the Power Platform API Audience.
 * @param settings - Configuration Settings to use.
 * @param cloud - Optional Power Platform Cloud Hosting Agent.
 * @param cloudBaseAddress - Optional Power Platform API endpoint to use if Cloud is configured as "other".
 * @param directConnectUrl - Optional DirectConnection URL to a given Copilot Studio agent, if provided all other settings are ignored.
 * @returns The Power Platform Audience.
 * @throws Will throw an error if required settings are missing or invalid.
 */
export function getTokenAudience (
  settings?: ConnectionSettings,
  cloud: PowerPlatformCloud = PowerPlatformCloud.Unknown,
  cloudBaseAddress: string = '',
  directConnectUrl: string = ''): string {
  if (!directConnectUrl && !settings?.directConnectUrl) {
    if (cloud === PowerPlatformCloud.Other && !cloudBaseAddress) {
      throw new Error('cloudBaseAddress must be provided when PowerPlatformCloudCategory is Other')
    }
    if (!settings && cloud === PowerPlatformCloud.Unknown) {
      throw new Error('Either settings or cloud must be provided')
    }
    if (settings && settings.cloud && settings.cloud !== PowerPlatformCloud.Unknown) {
      cloud = settings.cloud
    }
    if (cloud === PowerPlatformCloud.Other) {
      if (cloudBaseAddress && isValidUri(cloudBaseAddress)) {
        cloud = PowerPlatformCloud.Other
      } else if (settings?.customPowerPlatformCloud && isValidUri(settings!.customPowerPlatformCloud)) {
        cloud = PowerPlatformCloud.Other
        cloudBaseAddress = settings.customPowerPlatformCloud
      } else {
        throw new Error('Either CustomPowerPlatformCloud or cloudBaseAddress must be provided when PowerPlatformCloudCategory is Other')
      }
    }
    cloudBaseAddress ??= 'api.unknown.powerplatform.com'
    return `https://${getEndpointSuffix(cloud, cloudBaseAddress)}/.default`
  } else {
    if (!directConnectUrl) {
      directConnectUrl = settings?.directConnectUrl ?? ''
    }
    if (directConnectUrl && isValidUri(directConnectUrl)) {
      if (decodeCloudFromURI(new URL(directConnectUrl)) === PowerPlatformCloud.Unknown) {
        const cloudToTest: PowerPlatformCloud = settings?.cloud ?? cloud

        if (cloudToTest === PowerPlatformCloud.Other || cloudToTest === PowerPlatformCloud.Unknown) {
          throw new Error('Unable to resolve the PowerPlatform Cloud from DirectConnectUrl. The Token Audience resolver requires a specific PowerPlatformCloudCategory.')
        }
        if ((cloudToTest as PowerPlatformCloud) !== PowerPlatformCloud.Unknown) {
          return `https://${getEndpointSuffix(cloudToTest, '')}/.default`
        } else {
          throw new Error('Unable to resolve the PowerPlatform Cloud from DirectConnectUrl. The Token Audience resolver requires a specific PowerPlatformCloudCategory.')
        }
      }
      return `https://${getEndpointSuffix(decodeCloudFromURI(new URL(directConnectUrl)), '')}/.default`
    } else {
      throw new Error('DirectConnectUrl must be provided when DirectConnectUrl is set')
    }
  }
}
function isValidUri (uri: string): boolean {
  try {
    const absoluteUrl = uri.startsWith('http') ? uri : `https://${uri}`
    const newUri = new URL(absoluteUrl)
    return !!newUri
  } catch {
    return false
  }
}

function createURL (base: string, conversationId?: string): URL {
  const url = new URL(base)

  if (!url.searchParams.has('api-version')) {
    url.searchParams.append('api-version', '2022-03-01-preview')
  }

  if (url.pathname.endsWith('/')) {
    url.pathname = url.pathname.slice(0, -1)
  }

  if (url.pathname.includes('/conversations')) {
    url.pathname = url.pathname.substring(0, url.pathname.indexOf('/conversations'))
  }

  url.pathname = `${url.pathname}/conversations`
  if (conversationId) {
    url.pathname = `${url.pathname}/${conversationId}`
  }

  return url
}

function getEnvironmentEndpoint (
  cloud: PowerPlatformCloud,
  environmentId: string,
  cloudBaseAddress?: string
): URL {
  if (cloud === PowerPlatformCloud.Other && (!cloudBaseAddress || !cloudBaseAddress.trim())) {
    throw new Error('cloudBaseAddress must be provided when PowerPlatformCloud is Other')
  }

  cloudBaseAddress = cloudBaseAddress ?? 'api.unknown.powerplatform.com'

  const normalizedResourceId = environmentId.toLowerCase().replaceAll('-', '')
  const idSuffixLength = getIdSuffixLength(cloud)
  const hexPrefix = normalizedResourceId.substring(0, normalizedResourceId.length - idSuffixLength)
  const hexSuffix = normalizedResourceId.substring(normalizedResourceId.length - idSuffixLength)

  return new URL(`https://${hexPrefix}.${hexSuffix}.environment.${getEndpointSuffix(cloud, cloudBaseAddress)}`)
}

function getEndpointSuffix (
  category: PowerPlatformCloud,
  cloudBaseAddress: string
): string {
  switch (category) {
    case PowerPlatformCloud.Local:
      return 'api.powerplatform.localhost'
    case PowerPlatformCloud.Exp:
      return 'api.exp.powerplatform.com'
    case PowerPlatformCloud.Dev:
      return 'api.dev.powerplatform.com'
    case PowerPlatformCloud.Prv:
      return 'api.prv.powerplatform.com'
    case PowerPlatformCloud.Test:
      return 'api.test.powerplatform.com'
    case PowerPlatformCloud.Preprod:
      return 'api.preprod.powerplatform.com'
    case PowerPlatformCloud.FirstRelease:
    case PowerPlatformCloud.Prod:
      return 'api.powerplatform.com'
    case PowerPlatformCloud.GovFR:
      return 'api.gov.powerplatform.microsoft.us'
    case PowerPlatformCloud.Gov:
      return 'api.gov.powerplatform.microsoft.us'
    case PowerPlatformCloud.High:
      return 'api.high.powerplatform.microsoft.us'
    case PowerPlatformCloud.DoD:
      return 'api.appsplatform.us'
    case PowerPlatformCloud.Mooncake:
      return 'api.powerplatform.partner.microsoftonline.cn'
    case PowerPlatformCloud.Ex:
      return 'api.powerplatform.eaglex.ic.gov'
    case PowerPlatformCloud.Rx:
      return 'api.powerplatform.microsoft.scloud'
    case PowerPlatformCloud.Other:
      return cloudBaseAddress
    default:
      throw new Error(`Invalid cluster category value: ${category}`)
  }
}

function getIdSuffixLength (cloud: PowerPlatformCloud): number {
  switch (cloud) {
    case PowerPlatformCloud.FirstRelease:
    case PowerPlatformCloud.Prod:
      return 2
    default:
      return 1
  }
}

function decodeCloudFromURI (uri: URL): PowerPlatformCloud {
  const host = uri.host.toLowerCase()

  switch (host) {
    case 'api.powerplatform.localhost':
      return PowerPlatformCloud.Local
    case 'api.exp.powerplatform.com':
      return PowerPlatformCloud.Exp
    case 'api.dev.powerplatform.com':
      return PowerPlatformCloud.Dev
    case 'api.prv.powerplatform.com':
      return PowerPlatformCloud.Prv
    case 'api.test.powerplatform.com':
      return PowerPlatformCloud.Test
    case 'api.preprod.powerplatform.com':
      return PowerPlatformCloud.Preprod
    case 'api.powerplatform.com':
      return PowerPlatformCloud.Prod
    case 'api.gov.powerplatform.microsoft.us':
      return PowerPlatformCloud.GovFR
    case 'api.high.powerplatform.microsoft.us':
      return PowerPlatformCloud.High
    case 'api.appsplatform.us':
      return PowerPlatformCloud.DoD
    case 'api.powerplatform.partner.microsoftonline.cn':
      return PowerPlatformCloud.Mooncake
    default:
      return PowerPlatformCloud.Unknown
  }
}
