import type { FastifyInstance } from "fastify"
import type {
  PluginOptions,
  RegisterForFrontendGenerationOptions,
} from "../pluginTypes"
import type {
  PluginRegistration,
  TrackedRoute,
  GeneratedDataType,
  ParsedRouteInfo,
  CrudOperation,
} from "./generatorTypes"
import fs from "fs-extra"
import path from "path"
import eventMap from "@c/eventBus/eventBusEventMap"

// ========================================
// Registry Class (State Management)
// ========================================

class FrontendConfigRegistry {
  private static instance: FrontendConfigRegistry
  private registrations: Map<string, PluginRegistration> = new Map()
  private trackedRoutes: TrackedRoute[] = []
  private isGenerationPending = false
  private fastifyInstance: FastifyInstance | null = null
  private isTrackingSetup = false

  static getInstance = (): FrontendConfigRegistry => {
    if (!FrontendConfigRegistry.instance) {
      FrontendConfigRegistry.instance = new FrontendConfigRegistry()
    }
    return FrontendConfigRegistry.instance
  }

  registerPlugin = (
    pluginData: PluginOptions,
    options?: {
      entityMapping?: Record<string, string>
      customConfig?: PluginRegistration["customConfig"]
    },
  ): void => {
    const registration: PluginRegistration = {
      pluginName: pluginData.pluginName,
      author: pluginData.author,
      pluginKey: pluginData.key,
      pluginPath: pluginData.absolutePath,
      frontendPath: pluginData.frontendAbsolutePath,
      routePrefix: pluginData.routePrefix,
      entityMapping: options?.entityMapping || {},
      customConfig: options?.customConfig,
      registeredAt: Date.now(),
    }

    this.registrations.set(registration.pluginKey, registration)
    this.isGenerationPending = true
  }

  setFastifyInstance = (fastify: FastifyInstance): void => {
    this.fastifyInstance = fastify

    if (!this.isTrackingSetup) {
      this.setupRouteTracking(fastify)
      this.isTrackingSetup = true
    }

    if (fastify.log) {
      fastify.log.info(
        "Fastify instance registered for frontend generation with route tracking",
      )
    }
  }

  private setupRouteTracking = (fastify: FastifyInstance): void => {
    if (fastify.log) {
      fastify.log.info("Setting up route tracking for frontend generation")
    }

    fastify.addHook("onRoute", (routeOptions) => {
      if (
        routeOptions.url &&
        routeOptions.url !== "" &&
        routeOptions.url !== "/*" &&
        !Array.isArray(routeOptions.method) &&
        routeOptions.method.toUpperCase() !== "HEAD"
      ) {
        const trackedRoute: TrackedRoute = {
          method: Array.isArray(routeOptions.method)
            ? routeOptions.method[0]
            : routeOptions.method,
          url: routeOptions.url,
          routePath: routeOptions.url,
          config: routeOptions.config,
          schema: routeOptions.schema,
          handler: routeOptions.handler,
          routePrefix: routeOptions.prefix,
        }

        this.trackedRoutes.push(trackedRoute)
      }
    })

    fastify.addHook("onReady", async () => {
      if (fastify.log) {
        fastify.log.debug(
          `Route tracking complete. Tracked ${this.trackedRoutes.length} routes`,
        )
      }
    })
  }

  // Getters
  isReadyForGeneration = (): boolean =>
    this.fastifyInstance !== null &&
    this.isGenerationPending &&
    this.registrations.size > 0

  getRegistrations = (): PluginRegistration[] =>
    Array.from(this.registrations.values())

  getTrackedRoutes = (): TrackedRoute[] => this.trackedRoutes

  getFastifyInstance = (): FastifyInstance | null => this.fastifyInstance

  // State management
  markGenerationCompleted = (): void => {
    this.isGenerationPending = false
  }

  reset = (): void => {
    this.registrations.clear()
    this.trackedRoutes = []
    this.fastifyInstance = null
    this.isGenerationPending = false
    this.isTrackingSetup = false
  }
}

// ========================================
// Plugin Registration Functions (Called by Plugins)
// ========================================

export type {
  RegisterForFrontendGenerationOptions,
  RegisterForFrontendGenerationInput,
} from "../pluginTypes"

export const registerPluginForFrontendGeneration = (
  pluginOptions: PluginOptions,
  options?: RegisterForFrontendGenerationOptions,
): void => {
  const registry = FrontendConfigRegistry.getInstance()
  registry.registerPlugin(pluginOptions, options)
}

export const setFastifyInstanceForGeneration = (
  fastify: FastifyInstance,
): void => {
  const registry = FrontendConfigRegistry.getInstance()
  registry.setFastifyInstance(fastify)
}

// ========================================
// Generation Functions (Called by Core)
// ========================================

export const generateAllFrontendConfigs = async (): Promise<void> => {
  const registry = FrontendConfigRegistry.getInstance()

  if (!registry.isReadyForGeneration()) {
    const fastify = registry.getFastifyInstance()
    if (fastify?.log) {
      fastify.log.warn(
        "Frontend generation not ready - missing Fastify instance or no plugins registered",
      )
    }
    return
  }

  const fastify = registry.getFastifyInstance()!
  const registrations = registry.getRegistrations()

  if (fastify.log) {
    fastify.log.info(
      `Generating frontend configurations for ${registrations.length} registered plugins`,
    )
  }

  try {
    for (const registration of registrations) {
      await generatePluginFrontendConfig(fastify, registration)
    }

    registry.markGenerationCompleted()

    if (fastify.log) {
      fastify.log.info(
        `Frontend configurations generated successfully for ${registrations.length} plugins`,
      )
    }
  } catch (error) {
    if (fastify.log) {
      fastify.log.error("Error generating frontend configurations:", error)
    }
    throw error
  }
}

export const triggerGenerationIfReady = async (): Promise<void> => {
  const registry = FrontendConfigRegistry.getInstance()
  if (registry.isReadyForGeneration()) {
    await generateAllFrontendConfigs()
  }
}

// ========================================
// Internal Generation Logic
// ========================================

const generatePluginFrontendConfig = async (
  fastify: FastifyInstance,
  registration: PluginRegistration,
): Promise<void> => {
  const registry = FrontendConfigRegistry.getInstance()
  const allRoutes = registry.getTrackedRoutes()

  const pluginRoutes = allRoutes.filter((route) => {
    const routePrefix = registration.routePrefix || registration.pluginKey
    return (
      route.url.startsWith(`${routePrefix}`) || route.url === `${routePrefix}`
    )
  })

  if (pluginRoutes.length === 0) {
    if (fastify.log) {
      fastify.log.warn(`No routes found for plugin ${registration.pluginKey}`)
    }
    return
  }

  if (fastify.log) {
    fastify.log.info(
      `Processing ${pluginRoutes.length} routes for plugin ${registration.pluginKey}`,
    )
  }

  const dataTypes = new Map<string, GeneratedDataType>()

  for (const route of pluginRoutes) {
    const routeInfo = parseRouteInfo(route, registration)
    if (!routeInfo) continue

    const { entityName, operationName } = routeInfo

    if (!dataTypes.has(entityName)) {
      dataTypes.set(entityName, {
        type: entityName,
        operations: {},
      })
    }

    const dataType = dataTypes.get(entityName)!
    const operationConfig = generateOperationConfig(routeInfo)

    if (operationConfig) {
      dataType.operations[operationName] = operationConfig

      if (fastify.log) {
        fastify.log.debug(
          `Generated operation config: ${entityName}.${operationName} -> ${routeInfo.method} ${routeInfo.url}`,
        )
      }
    }
  }

  await writePluginConfigFile(dataTypes, registration, fastify)
}

// ========================================
// Route Parsing Utilities
// ========================================

const parseRouteInfo = (
  route: TrackedRoute,
  registration: PluginRegistration,
): ParsedRouteInfo | null => {
  const method = route.method || "GET"
  const url = route.url || route.routePath || ""

  const routePrefix = registration.routePrefix || registration.pluginKey
  const prefixPattern = new RegExp(`^/?${routePrefix}/?`)
  const routeAfterPrefix = url.replace(prefixPattern, "")

  if (!routeAfterPrefix) return null

  const segments = routeAfterPrefix
    .split("/")
    .filter((segment) => segment !== "")

  if (segments.length === 0) return null

  const routeKey = segments[0]
  const entityName = registration.entityMapping?.[routeKey] || routeKey
  const operationSegments = segments.slice(1)
  const pathParams = segments
    .filter((segment) => segment.startsWith(":"))
    .map((param) => param.substring(1))

  const operationName =
    route.config?.operationName ?? getOperationName(method, operationSegments)

  return {
    method,
    url,
    entityName,
    operationName,
    pathParams,
    registration,
    config: route.config || {},
    schema: route.schema || {},
  }
}

const getOperationName = (method: string, urlSegments: string[]): string => {
  const httpMethod = method.toLowerCase()
  const baseMethod = getMethodMapping(httpMethod)

  const nonParamSegments = urlSegments.filter(
    (segment) => !segment.startsWith(":"),
  )

  if (nonParamSegments.length === 0) {
    return baseMethod
  }

  const operationParts = nonParamSegments
    .map((segment) =>
      segment
        .split("-")
        .map((word, index) =>
          index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1),
        )
        .join(""),
    )
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join("")

  return baseMethod + operationParts
}

const getMethodMapping = (httpMethod: string): string => {
  const methodMapping: Record<string, string> = {
    get: "get",
    post: "create",
    patch: "update",
    put: "update",
    delete: "delete",
  }
  return methodMapping[httpMethod] || httpMethod
}

// ========================================
// Operation Configuration Generation
// ========================================

const generateOperationConfig = (
  routeInfo: ParsedRouteInfo,
): CrudOperation | null => {
  const {
    method,
    url,
    pathParams,
    registration,
    config: routeConfig,
  } = routeInfo

  if (!registration) return null

  const config: CrudOperation = {
    target: generateTargetFunction(url, pathParams),
    method: method.toLowerCase(),
  }
  if (routeConfig?.type) {
    config.storeActionType = routeConfig.type
  }
  if (routeInfo.config.broadcastChannels?.length) {
    config.channels = routeInfo.config.broadcastChannels.map(
      (channelName: string) => {
        const coreChannel = getCoreChannel(channelName)
        const channel: string =
          coreChannel || registration.pluginKey + "--" + channelName
        return {
          channel,
          channelKey: channelName,
          prefix: coreChannel ? "" : registration.pluginKey,
        }
      },
    )
  }

  return config
}

const getCoreChannel = (channelName: string): string | undefined => {
  if (Object.keys(eventMap).includes(channelName)) {
    if (channelName.startsWith("broadcast")) {
      const result = channelName.replace("broadcast", "")
      return result.charAt(0).toLowerCase() + result.slice(1)
    }
    return channelName
  }
  return undefined
}

const generateTargetFunction = (
  url: string,
  pathParams: string[],
): string | ((payload: any) => string) => {
  if (pathParams.length > 0) {
    let targetFunction = `(payload) => \`${url}\``

    for (const param of pathParams) {
      const paramPattern = new RegExp(`:${param}`, "g")
      targetFunction = targetFunction.replace(
        paramPattern,
        `\${payload.${param}}`,
      )
    }

    return targetFunction
  }

  return url
}

// ========================================
// File Generation Utilities
// ========================================

const writePluginConfigFile = async (
  dataTypes: Map<string, GeneratedDataType>,
  registration: PluginRegistration,
  fastify: FastifyInstance,
): Promise<void> => {
  if (dataTypes.size === 0) {
    if (fastify.log) {
      fastify.log.warn(
        `No data types generated for plugin ${registration.pluginKey}`,
      )
    }
    return
  }

  const configContent = generatePluginConfigFile(dataTypes, registration)
  const outputPath = path.join(
    registration.frontendPath,
    "generated-config.ts",
  )

  try {
    await fs.ensureDir(path.dirname(outputPath))
    await fs.writeFile(outputPath, configContent)

    if (fastify.log) {
      fastify.log.info(
        `Generated config for plugin ${registration.pluginKey}: ${outputPath}`,
      )
    }
  } catch (error) {
    if (fastify.log) {
      fastify.log.error(
        `Error writing config for plugin ${registration.pluginKey}:`,
        error,
      )
    }
  }
}

const generatePluginConfigFile = (
  dataTypes: Map<string, GeneratedDataType>,
  registration: PluginRegistration,
): string => {
  const dataObject: Record<string, any> = {}

  for (const [entityName, dataType] of dataTypes.entries()) {
    const routeKey =
      Object.keys(registration.entityMapping || {}).find(
        (key) => registration.entityMapping![key] === entityName,
      ) || entityName

    dataObject[routeKey] = {
      type: dataType.type,
      operations: dataType.operations,
    }
  }

  return `// Auto-generated frontend configuration for plugin: ${registration.pluginKey}
// This file is generated from backend routes - do not edit manually
// Entity mapping: ${JSON.stringify(registration.entityMapping || {})}

export default {
  pluginName: "${registration.pluginName}",
  author: "${registration.author}",
  pluginKey: "${registration.pluginKey}",
  routePrefix: "${registration.routePrefix}",
  pluginPath: "${registration.pluginPath}",
  data: ${JSON.stringify(dataObject, null, 2)
    .replace(/"(\([^)]+\) => [^"]+)"/g, "$1")
    .replace(/"([a-zA-Z_$][a-zA-Z0-9_$]*)"\s*:/g, "$1:")
    .split("\n")
    .map((line, index) => (index === 0 ? line : "  " + line))
    .join("\n")}
}
`
}
