require 'rack'
require 'faye/websocket'
require 'pathname'
require 'rack/handler/puma'

Faye::WebSocket.load_adapter('puma')

class AuthnK8sTestServer
    attr_reader :copied_content
    attr_reader :subpath
    attr_reader :bearer_token

    def initialize(subpath: "", bearer_token: "")
        @subpath = Pathname.new(subpath).to_s
        @bearer_token = bearer_token
    end

    def self.read_response_file(relative_path)
        File.read(File.join(File.dirname(__FILE__), "fixtures", relative_path))
    end

    def self.log(...)
        print("[authn-k8s test server] ")
        puts(...)
    end

    def self.run_server_instance(test_server)
        port = 1234

        Rack::Handler::Puma.run test_server, :Port => port, :Host => "::", :workers => 0 do |launcher|
            AuthnK8sTestServer.log("Server running with port=#{port}, @subpath=#{test_server.subpath}")

            yield(launcher) if block_given?
        end
    end

    # Runs the test server and blocks until there's an interrupt
    def self.run(...)
        # TODO: find out how to get random ports
        test_server = self.new(...)
        self.run_server_instance(test_server)
    end

    # Runs the test server in a non-main thread, then executes block in main thread. This function cleans up the test server when the block completes execution or raises an exception
    def self.run_async(...)
        if !block_given?
            raise "run_async requires a block"
        end

        test_server = self.new(...)
        launcher = nil
        thread = Thread.new do
            self.run_server_instance(test_server) do |_launcher|
                launcher = _launcher
            end
        end

        begin
            yield(test_server)
        ensure
            # clean up
            if launcher
                launcher.halt
            end
            thread.join
        end
    end

    def self.match_script(str)
        # Create regex from text generated by the bash_script method in CopyTextToFileInContainer.
        # The idea is to create slots in the text which correspond to the input arguments (path, content, mode), then to match those on
        # actual text sent over the wire.
        string_to_replace = "AAAA"
        path = content = mode = string_to_replace
        escaped = Authentication::AuthnK8s::CopyTextToFileInContainer.const_get("Call").new(*Array.new(10)).send(:bash_script, path, content, mode)
        escaped = Regexp.escape escaped
        escaped = escaped.gsub(string_to_replace, "(.*)")

        path, _, content, mode = str.match(Regexp.new(escaped, Regexp::MULTILINE)).captures
        [path, content, mode]
    end

    def ws_call(env)
        ws = Faye::WebSocket.new(env)
        r = Rack::Request.new(env)

        stdin = r.params["stdin"] == "true"

        def log_event(*args)
            AuthnK8sTestServer.log("Web socket event for request: #{args.flatten(1)}")
        end
        
        ws.on :open do |event|
            log_event([:open])
            next if stdin

            # Below is an example of the server writing to the client via websockets
            # ws.send("well then :)".bytes.unshift(1))
            ws.close(1000)
        end
        
        ws.on :message do |event|
            type, *message_bytes = event.data.bytes
            message = message_bytes.pack('c*')
            log_event([:message, type])
            # log_event([:message, type, message])

            path, content, mode = AuthnK8sTestServer.match_script(message)
            @copied_content = content
            # AuthnK8sTestServer.log("Updated @copied_content: \n#{copied_content}")
            ws.close(1000)
        end
    
    
        ws.on :close do |event|
          log_event([:close, event.code, event.reason])
          ws = nil
        end

        ws.on :error do |event|
          log_event([:error, event.inspect])
        end
    
        # Return async Rack response
        ws.rack_response
    end

    def call(env)
        req = Rack::Request.new(env)
        AuthnK8sTestServer.log("Handling request: #{req.request_method} path=#{req.fullpath}")

        if env['HTTP_AUTHORIZATION'] != "Bearer #{bearer_token}"
            return [ 401, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("bad_unauthorized.json")] ]
        end

        return ws_call(env) if Faye::WebSocket.websocket?(env)

        if req.path == "#{subpath}/api/v1"
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_api.v1.json")] ]
        elsif req.path == "#{subpath}/api"
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_api.json")] ]
        elsif req.path == "#{subpath}/apis"
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_apis.json")] ]
        elsif req.path.start_with?("#{subpath}/apis/")
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_apis.all.json")] ]
        elsif req.path == "#{subpath}/api/v1/namespaces/default/pods/bash-8449b79d7-c2fwd"
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_api.v1.getpod.json")] ]
        elsif req.path.start_with?("#{subpath}/api/v1/namespaces/default/pods/")
            [ 404, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("bad_api.v1.getpod.json")] ]
        elsif req.fullpath == "#{subpath}/api/v1/namespaces/default"
            [ 200, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("good_api.v1.getnamespace.json")]]
        elsif req.path.start_with?("#{subpath}/api/v1/namespaces")
            [ 404, {"Content-Type" => "application/json"}, [AuthnK8sTestServer.read_response_file("bad_api.v1.getnamespace.json")] ]
        # NOTE: Kubenertes clients make requests to a whole set of endpoints at initialization time. The only way we could find to make the clients
        # happy was to have this else branch return 200 with an empty JSON object. Ideally, this branch should return something like a 404.
        # TODO: Find a better way to satisfy Kubernetes client initialization in relation to the above note.
        else
            [ 200, {'Content-Type' => "application/json"}, ['{}'] ]
        end
    end
end
