# frozen_string_literal: true

require "base64"
require "json"

module Script
  module Layers
    module Infrastructure
      class ScriptService
        include SmartProperties
        property! :ctx, accepts: ShopifyCli::Context

        def push(
          uuid:,
          extension_point_type:,
          script_content:,
          compiled_type:,
          api_key: nil,
          force: false,
          metadata:,
          script_json:
        )
          query_name = "app_script_update_or_create"
          variables = {
            uuid: uuid,
            extensionPointName: extension_point_type.upcase,
            title: script_json.title,
            description: script_json.description,
            sourceCode: Base64.encode64(script_content),
            language: compiled_type,
            force: force,
            schemaMajorVersion: metadata.schema_major_version.to_s, # API expects string value
            schemaMinorVersion: metadata.schema_minor_version.to_s, # API expects string value
            useMsgpack: metadata.use_msgpack,
            scriptJsonVersion: script_json.version,
            configurationUi: script_json.configuration_ui,
            configurationDefinition: script_json.configuration&.to_json,
          }
          resp_hash = script_service_request(query_name: query_name, api_key: api_key, variables: variables)
          user_errors = resp_hash["data"]["appScriptUpdateOrCreate"]["userErrors"]

          return resp_hash["data"]["appScriptUpdateOrCreate"]["appScript"]["uuid"] if user_errors.empty?

          if user_errors.any? { |e| e["tag"] == "already_exists_error" }
            raise Errors::ScriptRepushError, uuid
          elsif (e = user_errors.any? { |err| err["tag"] == "configuration_syntax_error" })
            raise Errors::ScriptJsonSyntaxError
          elsif (e = user_errors.find { |err| err["tag"] == "configuration_definition_missing_keys_error" })
            raise Errors::ScriptJsonMissingKeysError, e["message"]
          elsif (e = user_errors.find { |err| err["tag"] == "configuration_definition_invalid_value_error" })
            raise Errors::ScriptJsonInvalidValueError, e["message"]
          elsif (e = user_errors.find do |err|
                   err["tag"] == "configuration_definition_schema_field_missing_keys_error"
                 end)
            raise Errors::ScriptJsonFieldsMissingKeysError, e["message"]
          elsif (e = user_errors.find do |err|
                   err["tag"] == "configuration_definition_schema_field_invalid_value_error"
                 end)
            raise Errors::ScriptJsonFieldsInvalidValueError, e["message"]
          elsif user_errors.find { |err| %w(not_use_msgpack_error schema_version_argument_error).include?(err["tag"]) }
            raise Domain::Errors::MetadataValidationError
          else
            raise Errors::GraphqlError, user_errors
          end
        end

        def get_app_scripts(api_key:, extension_point_type:)
          query_name = "get_app_scripts"
          variables = { appKey: api_key, extensionPointName: extension_point_type.upcase }
          script_service_request(query_name: query_name, api_key: api_key, variables: variables)["data"]["appScripts"]
        end

        private

        class ScriptServiceAPI < ShopifyCli::API
          property(:api_key, accepts: String)

          LOCAL_INSTANCE_URL = "https://script-service.myshopify.io"

          def self.query(ctx, query_name, api_key: nil, variables: {})
            api_client(ctx, api_key).query(query_name, variables: variables)
          end

          def self.api_client(ctx, api_key)
            instance_url = spin_instance_url || LOCAL_INSTANCE_URL
            new(
              ctx: ctx,
              url: "#{instance_url}/graphql",
              token: "",
              api_key: api_key
            )
          end

          def self.spin_instance_url
            workspace = ENV["SPIN_WORKSPACE"]
            namespace = ENV["SPIN_NAMESPACE"]
            return if workspace.nil? || namespace.nil?

            "https://script-service.#{workspace}.#{namespace}.us.spin.dev"
          end

          def auth_headers(*)
            tokens = { "APP_KEY" => api_key }.compact.to_json
            { "X-Shopify-Authenticated-Tokens" => tokens }
          end
        end
        private_constant(:ScriptServiceAPI)

        class PartnersProxyAPI < ShopifyCli::PartnersAPI
          def query(query_name, variables: {})
            variables[:query] = load_query(query_name)
            super("script_service_proxy", variables: variables)
          end
        end
        private_constant(:PartnersProxyAPI)

        def script_service_request(query_name:, variables: nil, **options)
          resp = if bypass_partners_proxy
            ScriptServiceAPI.query(ctx, query_name, variables: variables, **options)
          else
            proxy_through_partners(query_name: query_name, variables: variables, **options)
          end
          raise_if_graphql_failed(resp)
          resp
        end

        def bypass_partners_proxy
          !ENV["BYPASS_PARTNERS_PROXY"].nil?
        end

        def proxy_through_partners(query_name:, variables: nil, **options)
          options[:variables] = variables.to_json if variables
          resp = PartnersProxyAPI.query(ctx, query_name, **options)
          raise_if_graphql_failed(resp)
          JSON.parse(resp["data"]["scriptServiceProxy"])
        end

        def raise_if_graphql_failed(response)
          raise Errors::EmptyResponseError if response.nil?

          return unless response.key?("errors")
          case error_code(response["errors"])
          when "forbidden"
            raise Errors::ForbiddenError
          when "forbidden_on_shop"
            raise Errors::ShopAuthenticationError
          when "app_not_installed_on_shop"
            raise Errors::AppNotInstalledError
          else
            raise Errors::GraphqlError, response["errors"]
          end
        end

        def error_code(errors)
          errors.map do |e|
            code = e.dig("extensions", "code")
            return code if code
          end
        end
      end
    end
  end
end