-- SSE client for MCP HTTP Server-Sent Events
--
-- This module provides SSE client functionality for the MCP
-- Streamable HTTP transport, handling temporary SSE streams
-- per request as specified in the MCP standard.

local sse_client = {}

-- Check if we're in a Neovim environment
local is_neovim = _G.vim ~= nil

-- Try to load http_client with different paths
local http_client
local success, result = pcall(require, "paragonic.http_client")
if success then
	http_client = result
else
	-- Fallback to relative path
	success, result = pcall(require, "http_client")
	if success then
		-- Final fallback to absolute path
		http_client = require("../../lua/paragonic/http_client")
	end
end

-- SSE client configuration
local DEFAULT_TIMEOUT = 30 -- seconds

-- SSE client state
local client_state = {
	base_url = nil,
	session_id = nil,
	timeout = DEFAULT_TIMEOUT,
	callbacks = {},
	connection_client = nil,
	read_buffer = "",
	is_connected = false,
}

-- SSE event structure
local SSEEvent = {
	id = nil,
	event_type = nil,
	data = nil,
	retry = nil,
	timestamp = nil,
}

-- SSE client errors
local SSEClientError = {
	CONNECTION_FAILED = "connection_failed",
	INVALID_EVENT = "invalid_event",
	ALREADY_CONNECTED = "already_connected",
	NOT_CONNECTED = "not_connected",
	INVALID_URL = "invalid_url",
}

-- Initialize SSE client
function sse_client.init(config)
	config = config or {}

	client_state.base_url = config.base_url or "http://localhost:3000"
	client_state.timeout = config.timeout or DEFAULT_TIMEOUT

	-- Initialize HTTP client
	http_client.init({
		base_url = client_state.base_url,
		timeout = client_state.timeout,
		retry_attempts = 1, -- SSE handles its own retries
	})

	return true
end

-- Set session ID
function sse_client.set_session_id(session_id)
	if not session_id or type(session_id) ~= "string" then
		return false, "Invalid session ID"
	end

	client_state.session_id = session_id
	http_client.set_session_id(session_id)
	return true
end

-- Get current session ID
function sse_client.get_session_id()
	return client_state.session_id
end

-- Parse SSE event from text
function sse_client.parse_event(event_text)
	if not event_text or type(event_text) ~= "string" then
		return nil, "Invalid event text"
	end

	local event = {
		id = nil,
		event_type = nil,
		data = nil,
		retry = nil,
		timestamp = vim.loop.now(),
	}

	-- Parse event lines
	for line in event_text:gmatch("[^\r\n]+") do
		line = line:gsub("^%s+", ""):gsub("%s+$", "") -- trim whitespace

		if line == "" then
			-- Empty line indicates end of event
			break
		elseif line:match("^:") then
			-- Comment line, ignore
		elseif line:match("^id:") then
			event.id = line:sub(4):gsub("^%s+", ""):gsub("%s+$", "")
		elseif line:match("^event:") then
			event.event_type = line:sub(7):gsub("^%s+", ""):gsub("%s+$", "")
		elseif line:match("^data:") then
			local data = line:sub(6):gsub("^%s+", ""):gsub("%s+$", "")
			if event.data then
				event.data = event.data .. "\n" .. data
			else
				event.data = data
			end
		elseif line:match("^retry:") then
			local retry_str = line:sub(7):gsub("^%s+", ""):gsub("%s+$", "")
			event.retry = tonumber(retry_str)
		end
	end

	return event
end

-- Connect to SSE stream (temporary connection for single request)
function sse_client.connect(stream_id, callbacks)
	if client_state.is_connected then
		return false, SSEClientError.ALREADY_CONNECTED
	end

	-- Set callbacks
	client_state.callbacks = callbacks or {}

	-- Check if we're in a test environment (no real Neovim)
	local is_test_environment = not is_neovim

	if is_test_environment then
		-- In test environment, mark as connected
		client_state.is_connected = true
		if client_state.callbacks.on_connect then
			client_state.callbacks.on_connect(stream_id or "default")
		end
		return true
	else
		-- In real Neovim environment, establish actual SSE connection
		local success, client_or_err = pcall(function()
			return sse_client._establish_connection()
		end)

		if success and client_or_err then
			local client = client_or_err
			client_state.is_connected = true
			client_state.connection_client = client

			-- Log successful connection
			local debug = require("paragonic.debug")
			debug.debug_print_safe("✅ SSE connection established for temporary stream", "success")

			if client_state.callbacks.on_connect then
				client_state.callbacks.on_connect(stream_id or "default")
			end

			-- Set up async reading for SSE events
			sse_client._setup_async_reading(client)

			return true
		else
			-- For now, let's skip SSE connection if it fails and just return success
			-- This allows the MCP transport to work without SSE for basic functionality
			local debug = require("paragonic.debug")
			debug.debug_print_safe(
				"⚠️ SSE connection failed, continuing without SSE: " .. (client_or_err or "unknown error"),
				"warning"
			)
			debug.debug_print_safe("🔧 Falling back to non-SSE mode", "info")
			client_state.is_connected = true

			if client_state.callbacks.on_connect then
				client_state.callbacks.on_connect(stream_id or "default")
			end

			return true
		end
	end
end

-- Set up async reading for SSE events
function sse_client._setup_async_reading(client)
	-- Store buffer in client state for persistence
	client_state.read_buffer = ""
	client_state.http_headers_received = false

	-- Set up read callback
	client:read_start(function(err, data)
		if err then
			-- Handle error
			if client_state.callbacks.on_error then
				client_state.callbacks.on_error("SSE read error: " .. err, 0)
			end
			sse_client.disconnect()
			return
		end

		if not data then
			-- Connection closed
			sse_client.disconnect()
			return
		end

		-- Always log important SSE events (use safe version for async contexts)
		local debug = require("paragonic.debug")

		-- Log data reception (always)
		debug.debug_print_safe("📥 SSE Received data: " .. #data .. " bytes", "debug")

		-- Log detailed data in debug mode (sanitized for display)
		if vim.g.paragonic_debug_buffer then
			if #data < 100 then
				local sanitized_data = data:gsub("\r\n", "\\r\\n"):gsub("\r", "\\r"):gsub("\n", "\\n")
				debug.debug_print_safe("Data: " .. sanitized_data, "debug")
			end
		end

		-- Add data to buffer
		client_state.read_buffer = client_state.read_buffer .. data

		-- Check if we've received HTTP headers yet
		if not client_state.http_headers_received then
			-- Look for end of HTTP headers (double CRLF)
			local header_end = client_state.read_buffer:find("\r\n\r\n")
			if header_end then
				-- Extract and parse HTTP headers
				local headers_text = client_state.read_buffer:sub(1, header_end - 1)
				local status_line = headers_text:match("^([^\r\n]+)")

				if status_line then
					local status_code = status_line:match("HTTP/[%d%.]+%s+(%d+)")
					if status_code == "200" then
						-- Headers received successfully, start processing SSE events
						client_state.http_headers_received = true
						-- Remove headers from buffer, keep only SSE data
						client_state.read_buffer = client_state.read_buffer:sub(header_end + 4)

						-- Process any SSE events that came with the headers
						if #client_state.read_buffer > 0 then
							local events, remaining_buffer = sse_client._extract_events(client_state.read_buffer)
							client_state.read_buffer = remaining_buffer

							for _, event_text in ipairs(events) do
								local event, parse_err = sse_client.parse_event(event_text)
								if event then
									sse_client._handle_event(event)
								elseif client_state.callbacks.on_parse_error then
									client_state.callbacks.on_parse_error(parse_err, event_text)
								end
							end
						end
					else
						-- HTTP error
						if client_state.callbacks.on_error then
							client_state.callbacks.on_error("HTTP error: " .. status_line, 0)
						end
						sse_client.disconnect()
						return
					end
				else
					-- Invalid HTTP response
					if client_state.callbacks.on_error then
						client_state.callbacks.on_error("Invalid HTTP response", 0)
					end
					sse_client.disconnect()
					return
				end
			end
		else
			-- HTTP headers already received, process SSE events
			local events, remaining_buffer = sse_client._extract_events(client_state.read_buffer)
			client_state.read_buffer = remaining_buffer

			for _, event_text in ipairs(events) do
				local event, parse_err = sse_client.parse_event(event_text)
				if event then
					sse_client._handle_event(event)
				elseif client_state.callbacks.on_parse_error then
					client_state.callbacks.on_parse_error(parse_err, event_text)
				end
			end
		end
	end)
end

-- Extract complete SSE events from buffer
function sse_client._extract_events(buffer)
	local events = {}
	local lines = {}

	-- Split buffer into lines
	for line in buffer:gmatch("[^\r\n]*") do
		table.insert(lines, line)
	end

	-- Find complete events (separated by empty lines)
	local current_event = {}
	local last_empty = 0
	for i, line in ipairs(lines) do
		if line == "" then
			-- Empty line indicates end of event
			if #current_event > 0 then
				table.insert(events, table.concat(current_event, "\n"))
				current_event = {}
			end
			last_empty = i
		else
			table.insert(current_event, line)
		end
	end

	-- Return remaining buffer (incomplete events)
	local remaining_buffer = ""
	if last_empty > 0 and last_empty < #lines then
		remaining_buffer = table.concat(lines, "\n", last_empty + 1)
	end

	return events, remaining_buffer
end

-- Disconnect from SSE stream
function sse_client.disconnect()
	local debug = require("paragonic.debug")
	debug.debug_print_safe("🔌 SSE disconnect() called", "debug")

	if not client_state.is_connected then
		debug.debug_print_safe("🔌 SSE already disconnected", "debug")
		return false, SSEClientError.NOT_CONNECTED
	end

	debug.debug_print_safe("🔌 SSE disconnecting...", "debug")
	client_state.is_connected = false

	-- Close TCP client if exists
	if client_state.connection_client then
		client_state.connection_client:close()
		client_state.connection_client = nil
	end

	if client_state.callbacks.on_disconnect then
		client_state.callbacks.on_disconnect()
	end
	return true
end

-- Establish SSE connection using vim.uv for proper async streaming
function sse_client._establish_connection()
	local endpoint = "/mcp"
	-- Note: The server creates the stream internally based on the session ID
	-- We don't need to pass the stream ID as a query parameter

	local url = client_state.base_url .. endpoint

	-- Build headers string for vim.uv
	local headers = {
		"Accept: text/event-stream",
		"Cache-Control: no-cache",
		"mcp-protocol-version: 2025-06-18",
		"origin: neovim://paragonic",
	}

	-- Add session ID if available
	if client_state.session_id then
		table.insert(headers, "mcp-session-id: " .. client_state.session_id)
	end

	-- Use vim.uv for async HTTP request (if available)
	local client
	if is_neovim then
		client = vim.uv.new_tcp()
	else
		-- Fallback for non-Neovim environment
		return nil, "vim.uv not available in this environment"
	end

	-- Parse URL to get host and port
	local host, port
	if url:match("^https://") then
		host = url:match("^https://([^:/]+)")
		port = url:match(":([0-9]+)") or 443
	else
		host = url:match("^http://([^:/]+)")
		port = url:match(":([0-9]+)") or 80
	end

	if not host then
		return nil, "Invalid URL: " .. url
	end

	-- Convert port to number
	port = tonumber(port) or 80

	-- For localhost, use 127.0.0.1
	if host == "localhost" then
		host = "127.0.0.1"
	end

	-- Connect to server
	local success, err = client:connect(host, port)
	if not success then
		return nil, "Connection failed: " .. (err or "unknown error")
	end

	-- Build HTTP request
	local request = string.format(
		"GET %s HTTP/1.1\r\n" .. "Host: %s\r\n" .. "%s\r\n" .. "\r\n",
		endpoint,
		host,
		table.concat(headers, "\r\n")
	)

	-- Always log SSE connection attempts
	local debug = require("paragonic.debug")
	debug.debug_print_safe("🔍 SSE Request:", "debug")

	-- Log detailed request in debug mode (sanitized for display)
	if vim.g.paragonic_debug_buffer then
		local sanitized_request = request:gsub("\r\n", "\\r\\n"):gsub("\r", "\\r"):gsub("\n", "\\n")
		debug.debug_print_safe(sanitized_request, "debug")
	end

	-- Send request
	client:write(request)

	-- Return the client for streaming
	return client
end

-- Handle parsed SSE event
function sse_client._handle_event(event)
	-- Log event handling
	local debug = require("paragonic.debug")
	debug.debug_print_safe(
		"📨 SSE Event received - Type: " .. (event.event_type or "message") .. ", ID: " .. (event.id or "none"),
		"debug"
	)

	-- Log connection status
	debug.debug_print_safe(
		"🔗 SSE Connection status: " .. (client_state.is_connected and "connected" or "disconnected"),
		"debug"
	)

	-- Trigger appropriate callback
	if event.event_type == "message" or not event.event_type then
		if client_state.callbacks.on_message then
			client_state.callbacks.on_message(event)
		end
	elseif event.event_type == "notification" then
		if client_state.callbacks.on_notification then
			client_state.callbacks.on_notification(event)
		end
	elseif event.event_type == "error" then
		if client_state.callbacks.on_error then
			client_state.callbacks.on_error(event.data, 0)
		end
	end

	-- Trigger generic event callback
	if client_state.callbacks.on_event then
		client_state.callbacks.on_event(event)
	end
end

-- Check if connected
function sse_client.is_connected()
	return client_state.is_connected
end

-- Get connection status
function sse_client.get_connection_status()
	return {
		is_connected = client_state.is_connected,
		session_id = client_state.session_id,
	}
end

-- Clean up resources
function sse_client.cleanup()
	-- Disconnect if connected
	if client_state.is_connected then
		sse_client.disconnect()
	end

	-- Clear state
	client_state = {
		base_url = nil,
		session_id = nil,
		timeout = DEFAULT_TIMEOUT,
		callbacks = {},
		connection_client = nil,
		read_buffer = "",
		is_connected = false,
	}

	-- Clean up HTTP client
	http_client.cleanup()
end

-- Export module
return sse_client
