defmodule AshCommanded.Commanded.Transformers.GenerateCommandedApplication do
  @moduledoc """
  Generates a Commanded.Application module based on the configuration in the application section.

  This transformer:
  1. Extracts application configuration from the DSL
  2. Generates a module that uses Commanded.Application
  3. Configures it with the options from the application section
  4. Includes supervision tree integration if specified
  """

  use Spark.Dsl.Transformer

  alias Spark.Dsl.Transformer
  alias AshCommanded.Commanded.Transformers.BaseTransformer

  @doc """
  Transforms the DSL state by adding the generated Commanded application module.

  ## Parameters

  * `dsl` - The DSL state to transform.

  ## Returns

  The transformed DSL state with the generated Commanded application module added.

  ## Examples

      iex> transform(dsl)
      %{dsl | modules: dsl.modules ++ [generated_module_ast]}
  """
  @impl true
  @spec transform(Transformer.t()) :: {:ok, Transformer.t()} | {:error, term()}
  def transform(dsl) do
    case has_commanded?() do
      true ->
        # Only generate if this is a domain (has resources)
        case get_domain_resources(dsl) do
          [] -> 
            {:ok, dsl}
          _resources ->
            do_transform(dsl)
        end
      false ->
        {:ok, dsl}
    end
  end

  @doc false
  @impl true
  def after?(AshCommanded.Commanded.Transformers.GenerateMainRouterModule), do: true
  def after?(_), do: false

  # Private Functions

  defp do_transform(dsl) do
    case application_config = AshCommanded.Commanded.Dsl.application(dsl) do
      nil ->
        {:ok, dsl}

      _config ->
        app_module = application_module_name(dsl, application_config)
        router_module = main_router_module_name(dsl)
        
        code =
          generate_application_module(
            app_module,
            router_module,
            application_config
          )

        # Create the module at compile time
        BaseTransformer.create_module(app_module, code, __ENV__)
        
        # Store a reference to the application module in the DSL state
        updated_dsl = Transformer.persist(dsl, :commanded_application_module, app_module)
        
        {:ok, updated_dsl}
    end
  end

  defp generate_application_module(app_module, router_module, config) do
    include_supervisor? = Keyword.get(config, :include_supervisor?, false)
    otp_app = Keyword.get(config, :otp_app)
    event_store = Keyword.get(config, :event_store)
    pubsub = Keyword.get(config, :pubsub)
    registry = Keyword.get(config, :registry)
    
    # Extract snapshotting configuration
    snapshotting_enabled = Keyword.get(config, :snapshotting, false)
    snapshot_threshold = Keyword.get(config, :snapshot_threshold, 100)
    snapshot_version = Keyword.get(config, :snapshot_version, 1)
    snapshot_store = Keyword.get(config, :snapshot_store, nil)
    
    _snapshot_config = if snapshotting_enabled, do: [
      threshold: snapshot_threshold,
      snapshot_version: snapshot_version
    ], else: []

    supervisor_child_specs = 
      if include_supervisor? do
        # Find projector modules that need to be supervised
        # For now, just using a simple supervisor
        quote do
          @impl true
          def child_spec() do
            # TODO: Add dynamically discovered projectors here
            Supervisor.child_spec(
              {Supervisor, [
                strategy: :one_for_one,
                name: Module.concat(unquote(app_module), Supervisor)
              ]},
              id: Module.concat(unquote(app_module), Supervisor)
            )
          end
        end
      else
        quote do end
      end

    # Generate configuration options
    config_lines = []

    config_lines =
      if is_atom(event_store) do
        config_lines ++ [{:event_store, event_store}]
      else
        config_lines ++ [{:event_store, Macro.escape(event_store)}]
      end

    config_lines = config_lines ++ [{:pubsub, pubsub}]
    config_lines = config_lines ++ [{:registry, registry}]
    
    # Add snapshotting configuration if enabled
    config_lines =
      if snapshotting_enabled do
        snapshot_opts = [
          snapshot_every: snapshot_threshold,
          snapshot_module: AshCommanded.Commanded.SnapshotAdapter
        ]
        config_lines ++ [{:snapshotting, snapshot_opts}]
      else
        config_lines
      end

    config_lines = config_lines ++ [{:router, router_module}]

    # Initialize snapshot store if snapshotting is enabled
    snapshot_init = 
      if snapshotting_enabled do
        quote do
          @doc """
          Initializes the snapshot store when the application starts.
          
          This is called automatically by Commanded during application startup.
          """
          @impl true
          def init do
            # Initialize the default snapshot store or a custom one if provided
            snapshot_store = unquote(snapshot_store) || AshCommanded.Commanded.SnapshotStore
            
            # Initialize the snapshot store with application configuration
            case snapshot_store.init(%{
              threshold: unquote(snapshot_threshold),
              version: unquote(snapshot_version)
            }) do
              :ok -> :ok
              {:error, reason} ->
                require Logger
                Logger.warning("Failed to initialize snapshot store: #{inspect(reason)}")
                :ok
            end
            
            :ok
          end
        end
      else
        quote do end
      end
      
    quote do
      defmodule unquote(app_module) do
        @moduledoc """
        A Commanded application for managing aggregates and event handlers.

        This module is automatically generated by the AshCommanded extension
        based on the application configuration in the DSL.
        """

        use Commanded.Application, [
          otp_app: unquote(otp_app)
        ] ++ unquote(Macro.escape(config_lines))

        unquote(supervisor_child_specs)
        
        unquote(snapshot_init)
      end
    end
  end

  defp application_module_name(dsl, config) do
    prefix = Keyword.get(config, :prefix)

    domain_name =
      dsl
      |> Spark.Dsl.Extension.get_persisted(:domain_name)
      |> Module.split()
      |> List.last()

    if prefix do
      module_name = Module.concat([prefix, "#{domain_name}Application"])
      if String.starts_with?(to_string(prefix), "Elixir.") do
        module_name
      else
        Module.concat(Elixir, module_name)
      end
    else
      domain_module =
        dsl
        |> Spark.Dsl.Extension.get_persisted(:domain_name)

      Module.concat(domain_module, "Application")
    end
  end

  defp main_router_module_name(dsl) do
    domain_name = Spark.Dsl.Extension.get_persisted(dsl, :domain_name)
    
    if domain_name do
      Module.concat(domain_name, "Router")
    else
      AshCommanded.Router
    end
  end

  defp get_domain_resources(dsl) do
    case Spark.Dsl.Extension.get_entities(dsl, [:resources]) do
      {:ok, resources} -> resources
      _ -> []
    end
  end

  defp has_commanded? do
    Code.ensure_loaded?(Commanded.Application)
  end
end