from flask import Flask, request, jsonify
from datetime import datetime
import logging

# Configure logging with more detailed format
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('radio_server.log'),
        logging.StreamHandler()
    ]
)

app = Flask(__name__)

# In-memory storage
messages = []
connected_servers = set()
message_id_counter = 0

# Configure Flask for better timeout handling
app.config['PROPAGATE_EXCEPTIONS'] = True

# Add CORS headers to allow cross-origin requests
@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Server-ID')
    response.headers.add('Access-Control-Allow-Methods', 'GET,POST,OPTIONS')
    return response

@app.route('/connect', methods=['POST', 'OPTIONS'])
def connect():
    if request.method == 'OPTIONS':
        return jsonify({'success': True})
        
    server_id = request.headers.get('Server-ID')
    if not server_id:
        logging.warning("Connection attempt without Server-ID")
        return jsonify({'success': False, 'error': 'Server-ID required'}), 400
        
    connected_servers.add(server_id)
    logging.info(f"Server {server_id} connected. Total connected servers: {len(connected_servers)}")
    return jsonify({'success': True})

@app.route('/disconnect', methods=['POST'])
def disconnect():
    server_id = request.headers.get('Server-ID')
    if server_id and server_id in connected_servers:
        connected_servers.remove(server_id)
        logging.info(f"Server {server_id} disconnected. Remaining servers: {len(connected_servers)}")
    return jsonify({'success': True})

@app.route('/message', methods=['POST'])
def send_message():
    global message_id_counter
    
    server_id = request.headers.get('Server-ID')
    if not server_id or server_id not in connected_servers:
        return jsonify({'error': 'Not connected'}), 403
        
    try:
        data = request.get_json()
        if not data or 'channel' not in data or 'content' not in data:
            return jsonify({'error': 'Invalid message format'}), 400
            
        message_id_counter += 1
        message = {
            'id': message_id_counter,
            'channel': data['channel'],
            'content': data['content'],
            'server': server_id,
            'timestamp': datetime.now().isoformat()
        }
        messages.append(message)
        
        # Keep only last 1000 messages
        if len(messages) > 1000:
            messages.pop(0)
            
        logging.info(f"New message from {server_id} on channel {data['channel']}")
        return jsonify({'success': True, 'message_id': message_id_counter})
    except Exception as e:
        logging.error(f"Error processing message: {str(e)}")
        return jsonify({'error': 'Internal server error'}), 500

@app.route('/messages', methods=['GET'])
def get_messages():
    try:
        after_id = int(request.args.get('after', 0))
        new_messages = [msg for msg in messages if msg['id'] > after_id]
        return jsonify({'messages': new_messages})
    except ValueError:
        return jsonify({'error': 'Invalid after_id parameter'}), 400
    except Exception as e:
        logging.error(f"Error retrieving messages: {str(e)}")
        return jsonify({'error': 'Internal server error'}), 500

if __name__ == '__main__':
    # Increase timeout values and enable threaded mode
    app.run(host='0.0.0.0', port=3000, threaded=True)

ENT.Type = "anim"
ENT.Base = "base_gmodentity"
ENT.PrintName = "Cross-Server Radio"
ENT.Author = "Your Name"
ENT.Spawnable = true
ENT.AdminOnly = false

-- Debug function
local function DebugLog(msg, ...)
    local formatted = string.format(msg, ...)
    print("[Radio Debug] " .. formatted)
    if SERVER then
        ServerLog("[Radio Debug] " .. formatted .. "\n")
    end
end

function ENT:SetupDataTables()
    self:NetworkVar("Entity", 0, "owning_ent")
    self:NetworkVar("Int", 0, "Channel")
    self:NetworkVar("Bool", 0, "Broadcasting")
end

function ENT:IsRadio()
    return true
end

if CLIENT then
    local WINDOW_WIDTH = 400
    local WINDOW_HEIGHT = 300
    local MARGIN = 10
    
    -- Create sound effect objects
    local connectSound = Sound("buttons/button17.wav")
    local disconnectSound = Sound("buttons/button16.wav")
    local messageSound = Sound("buttons/lightswitch2.wav")
    local errorSound = Sound("buttons/button10.wav")
    
    -- Initialize frame variable in global scope
    local frame = nil
    
    net.Receive("OpenRadioMenu", function()
        DebugLog("Opening radio menu")
        if IsValid(frame) then 
            frame:Close()
            return 
        end
        
        frame = vgui.Create("DFrame")
        frame:SetSize(WINDOW_WIDTH, WINDOW_HEIGHT)
        frame:SetTitle("Secure Communications Terminal")
        frame:Center()
        frame:MakePopup()
        
        -- Status panel
        local statusPanel = vgui.Create("DPanel", frame)
        statusPanel:SetPos(MARGIN, 30)
        statusPanel:SetSize(WINDOW_WIDTH - (MARGIN * 2), 25)
        statusPanel.Paint = function(self, w, h)
            draw.RoundedBox(0, 0, 0, w, h, Color(30, 30, 30))
        end
        
        local statusLabel = vgui.Create("DLabel", statusPanel)
        statusLabel:SetPos(5, 5)
        statusLabel:SetText("Status: Disconnected")
        statusLabel:SetTextColor(Color(255, 100, 100))
        statusLabel:SizeToContents()
        
        -- Channel input
        local channelWang = vgui.Create("DNumberWang", frame)
        channelWang:SetPos(WINDOW_WIDTH - 80 - MARGIN, 32)
        channelWang:SetSize(60, 20)
        channelWang:SetMinMax(1, 999)
        channelWang:SetValue(1)
        channelWang:SetDecimals(0)
        channelWang:SetEnabled(false)
        
        -- Connect/Disconnect button
        local connectBtn = vgui.Create("DButton", frame)
        connectBtn:SetPos(MARGIN, 65)
        connectBtn:SetSize(WINDOW_WIDTH - (MARGIN * 2), 25)
        connectBtn:SetText("Connect to Network")
        connectBtn:SetTextColor(Color(255, 255, 255))
        
        -- Message panel (hidden initially)
        local messagePanel = vgui.Create("DPanel", frame)
        messagePanel:SetPos(MARGIN, 100)
        messagePanel:SetSize(WINDOW_WIDTH - (MARGIN * 2), WINDOW_HEIGHT - 110)
        messagePanel:SetVisible(false)
        messagePanel.Paint = function(self, w, h)
            draw.RoundedBox(0, 0, 0, w, h, Color(30, 30, 30))
        end
        
        -- Message history
        local messageList = vgui.Create("DListView", messagePanel)
        messageList:SetPos(0, 0)
        messageList:SetSize(WINDOW_WIDTH - (MARGIN * 2), WINDOW_HEIGHT - 145)
        messageList:AddColumn("Time")
        messageList:AddColumn("Channel")
        messageList:AddColumn("Message")
        messageList:SetHeaderHeight(20)
        
        -- Message input
        local messageEntry = vgui.Create("DTextEntry", messagePanel)
        messageEntry:SetPos(0, WINDOW_HEIGHT - 145)
        messageEntry:SetSize(WINDOW_WIDTH - (MARGIN * 2), 25)
        messageEntry:SetPlaceholderText("Enter message...")
        messageEntry:SetEnabled(false)
        
        local radioEnt = LocalPlayer():GetEyeTrace().Entity
        local isConnected = false
        local reconnectAttempts = 0
        local MAX_RECONNECT_ATTEMPTS = 3
        
        -- Update connection status
        net.Receive("RadioConnectionStatus", function()
            local connected = net.ReadBool()
            local errorMsg = net.ReadString()
            isConnected = connected
            
            DebugLog("Connection status updated: %s %s", connected and "Connected" or "Disconnected", errorMsg != "" and ("Error: " .. errorMsg) or "")
            
            local statusText = "Status: " .. (connected and "Connected" or "Disconnected")
            if errorMsg != "" then
                statusText = statusText .. " (" .. errorMsg .. ")"
                surface.PlaySound(errorSound)
            else
                surface.PlaySound(connected and connectSound or disconnectSound)
            end
            
            statusLabel:SetText(statusText)
            statusLabel:SetTextColor(connected and Color(100, 255, 100) or Color(255, 100, 100))
            statusLabel:SizeToContents()
            
            connectBtn:SetText(connected and "Disconnect" or "Connect to Network")
            messageEntry:SetEnabled(connected)
            channelWang:SetEnabled(connected)
            messagePanel:SetVisible(connected)
            
            -- Reset reconnect attempts on successful connection
            if connected then
                reconnectAttempts = 0
            end
        end)
        
        -- Handle connection button
        connectBtn.DoClick = function()
            DebugLog("Connect button clicked. Current status: %s", isConnected and "Connected" or "Disconnected")
            if not isConnected then
                if reconnectAttempts >= MAX_RECONNECT_ATTEMPTS then
                    statusLabel:SetText("Status: Too many failed attempts. Please try again later.")
                    statusLabel:SetTextColor(Color(255, 100, 100))
                    statusLabel:SizeToContents()
                    surface.PlaySound(errorSound)
                    return
                end
                
                reconnectAttempts = reconnectAttempts + 1
                net.Start("RadioConnect")
                net.WriteEntity(radioEnt)
                net.SendToServer()
                messagePanel:SetVisible(true)
            else
                net.Start("RadioDisconnect")
                net.WriteEntity(radioEnt)
                net.SendToServer()
                messagePanel:SetVisible(false)
            end
        end
        
        -- Message input handler
        messageEntry.OnEnter = function(self)
            local message = self:GetValue()
            if message != "" and isConnected then
                DebugLog("Sending message on channel %d: %s", channelWang:GetValue(), message)
                net.Start("SendRadioMessage")
                net.WriteEntity(radioEnt)
                net.WriteInt(channelWang:GetValue(), 32)
                net.WriteString(message)
                net.SendToServer()
                
                surface.PlaySound(messageSound)
                self:SetValue("")
            end
        end
        
        -- Receive messages from server
        net.Receive("ReceiveRadioMessage", function()
            local channel = net.ReadInt(32)
            local message = net.ReadString()
            local timeStr = os.date("%H:%M:%S")
            
            DebugLog("Received message on channel %d: %s", channel, message)
            messageList:AddLine(timeStr, channel, message)
            messageList:SortByColumn(1, true)
            messageList:ScrollToItem(messageList:GetLine(messageList:GetLineCount()))
            
            -- Play message sound only if it's not our own message
            if not string.StartWith(message, LocalPlayer():Nick()) then
                surface.PlaySound(messageSound)
            end
        end)
        
        -- Clean up on close
        frame.OnRemove = function()
            DebugLog("Closing radio menu")
            if isConnected then
                net.Start("RadioDisconnect")
                net.WriteEntity(radioEnt)
                net.SendToServer()
            end
            frame = nil
        end
    end)
end

if SERVER then
    util.AddNetworkString("OpenRadioMenu")
    util.AddNetworkString("RadioConnect")
    util.AddNetworkString("RadioDisconnect")
    util.AddNetworkString("SendRadioMessage")
    util.AddNetworkString("ReceiveRadioMessage")
    util.AddNetworkString("RadioConnectionStatus")
    
    local API_URL = " http://e46c-88-141-148-196.ngrok-free.app"  -- Change this to your server's URL
    local connectedPlayers = {}
    local pollingTimers = {}
    local lastMessageId = 0
    local requestQueue = {}
    local isProcessingQueue = false
    
    -- Updated request configuration
    local function makeRequest(method, endpoint, data, callback)
        HTTP({
            method = method,
            url = API_URL .. endpoint,
            headers = {
                ["Content-Type"] = "application/json",
                ["Server-ID"] = GetHostName()
            },
            body = data and util.TableToJSON(data) or nil,
            timeout = 30, -- Increased timeout
            success = function(code, body, headers)
                DebugLog("Request successful: %s %s (Status: %d)", method, endpoint, code)
                
                if code == 200 and callback then
                    local success, response = pcall(util.JSONToTable, body)
                    if success then
                        callback(response)
                    else
                        DebugLog("Failed to parse JSON response: %s", response)
                        callback({ success = false, error = "Invalid response" })
                    end
                else
                    callback({ success = false, error = "Server error" })
                end
            end,
            failed = function(err)
                DebugLog("Request failed: %s %s (Error: %s)", method, endpoint, err)
                if callback then
                    callback({ success = false, error = "Connection failed" })
                end
            end
        })
    end

    -- Function to process the request queue
    local function processRequestQueue()
        if isProcessingQueue or #requestQueue == 0 then return end
        
        isProcessingQueue = true
        local request = table.remove(requestQueue, 1)
        
        makeRequest(request.method, request.endpoint, request.data, function(response)
            if request.callback then
                request.callback(response)
            end
            isProcessingQueue = false
            timer.Simple(0.1, processRequestQueue)
        end)
    end
    
    -- Function to queue requests
    local function queueRequest(endpoint, method, data, callback)
        DebugLog("Queueing request: %s %s", method, endpoint)
        
        table.insert(requestQueue, {
            endpoint = endpoint,
            method = method,
            data = data,
            callback = callback
        })
        
        processRequestQueue()
    end
    
    -- Start polling for new messages
    local function startPolling(ply)
        if pollingTimers[ply] then
            DebugLog("Polling already active for player %s", ply:Nick())
            return
        end
        
        DebugLog("Starting polling for player %s", ply:Nick())
        
        pollingTimers[ply] = timer.Create("RadioPoll_" .. ply:SteamID64(), 2, 0, function()
            if not IsValid(ply) or not connectedPlayers[ply] then
                DebugLog("Stopping polling for player %s (invalid or disconnected)", ply:Nick())
                timer.Remove("RadioPoll_" .. ply:SteamID64())
                pollingTimers[ply] = nil
                return
            end
            
            queueRequest("/messages?after=" .. lastMessageId, "GET", nil, function(response)
                if response.success == false then
                    -- Handle connection error
                    net.Start("RadioConnectionStatus")
                    net.WriteBool(false)
                    net.WriteString(response.error or "Connection lost")
                    net.Send(ply)
                    return
                end
                
                if response.messages then
                    DebugLog("Received %d new messages", #response.messages)
                    for _, msg in ipairs(response.messages) do
                        lastMessageId = math.max(lastMessageId, msg.id)
                        
                        net.Start("ReceiveRadioMessage")
                        net.WriteInt(tonumber(msg.channel), 32)
                        net.WriteString(msg.content)
                        net.Send(ply)
                    end
                end
            end)
        end)
    end
    
    -- Handle connect request
    net.Receive("RadioConnect", function(len, ply)
        local radioEnt = net.ReadEntity()
        
        DebugLog("Connect request from player %s", ply:Nick())
        
        if not IsValid(radioEnt) or not radioEnt:IsRadio() then
            DebugLog("Invalid radio entity for player %s", ply:Nick())
            net.Start("RadioConnectionStatus")
            net.WriteBool(false)
            net.WriteString("Invalid radio")
            net.Send(ply)
            return
        end
        
        queueRequest("/connect", "POST", nil, function(response)
            if response.success then
                DebugLog("Player %s connected successfully", ply:Nick())
                connectedPlayers[ply] = true
                startPolling(ply)
                
                net.Start("RadioConnectionStatus")
                net.WriteBool(true)
                net.WriteString("")
                net.Send(ply)
            else
                DebugLog("Connection failed for player %s: %s", ply:Nick(), response.error)
                net.Start("RadioConnectionStatus")
                net.WriteBool(false)
                net.WriteString(response.error)
                net.Send(ply)
            end
        end)
    end)
    
    -- Handle disconnect request
    net.Receive("RadioDisconnect", function(len, ply)
        DebugLog("Disconnect request from player %s", ply:Nick())
        
        if pollingTimers[ply] then
            timer.Remove("RadioPoll_" .. ply:SteamID64())
            pollingTimers[ply] = nil
        end
        
        connectedPlayers[ply] = nil
        
        queueRequest("/disconnect", "POST", nil, function(response)
            DebugLog("Player %s disconnected", ply:Nick())
        end)
        
        net.Start("RadioConnectionStatus")
        net.WriteBool(false)
        net.WriteString("")
        net.Send(ply)
    end)
    
    -- Handle messages
    net.Receive("SendRadioMessage", function(len, ply)
        if not connectedPlayers[ply] then
            DebugLog("Rejected message from disconnected player %s", ply:Nick())
            return
        end
        
        local radioEnt = net.ReadEntity()
        local channel = net.ReadInt(32)
        local message = net.ReadString()
        
        DebugLog("Message from %s on channel %d: %s", ply:Nick(), channel, message)
        
        if IsValid(radioEnt) and radioEnt:IsRadio() then
            local data = {
                channel = tostring(channel),
                content = ply:Nick() .. ": " .. message,
                sender = ply:SteamID()
            }
            
            queueRequest("/message", "POST", data, function(response)
                DebugLog("Message sent successfully from %s", ply:Nick())
            end)
            
            -- Local feedback
            net.Start("ReceiveRadioMessage")
            net.WriteInt(channel, 32)
            net.WriteString(data.content)
            net.Broadcast()
        else
            DebugLog("Invalid radio entity for message from %s", ply:Nick())
        end
    end)
    
    -- Handle player disconnect
    hook.Add("PlayerDisconnected", "CleanupRadioConnection", function(ply)
        DebugLog("Player %s disconnected from server", ply:Nick())
        if pollingTimers[ply] then
            timer.Remove("RadioPoll_" .. ply:SteamID64())
            pollingTimers[ply] = nil
        end
        connectedPlayers[ply] = nil
    end)
    
    -- Clean up on shutdown
    hook.Add("ShutDown", "CleanupRadioConnections", function()
        DebugLog("Server shutting down, cleaning up radio connections")
        queueRequest("/disconnect", "POST", nil)
    end)
end

[Radio Debug] Opening radio menu
[Radio Debug] Connect button clicked. Current status: Disconnected
[Radio Debug] Connect request from player joober97
ServerLog: [Radio Debug] Connect request from player joober97
[Radio Debug] Queueing request: POST /connect
ServerLog: [Radio Debug] Queueing request: POST /connect
[Radio Debug] Request failed: POST /connect (Error: invalid url or GET parameters)
ServerLog: [Radio Debug] Request failed: POST /connect (Error: invalid url or GET parameters)
[Radio Debug] Connection failed for player joober97: Connection failed
ServerLog: [Radio Debug] Connection failed for player joober97: Connection failed
[Radio Debug] Connection status updated: Disconnected Error: Connection failed
[Radio Debug] Closing radio menu

02:07:59.734 CESTPOST /disconnect               200 OK                                                                  02:07:58.036 CESTGET  /messages                 200 OK                                                                  02:07:56.036 CESTGET  /messages                 200 OK                                                                  02:07:56.529 CESTPOST /message                  500 INTERNAL SERVER ERROR                                               02:07:53.851 CESTPOST /connect                  200 OK                                                                  02:07:45.399 CESTPOST /disconnect               200 OK                                                                  02:07:44.265 CESTGET  /messages                 200 OK                                                                  02:07:42.450 CESTPOST /message                  500 INTERNAL SERVER ERROR                                               02:07:42.250 CESTGET  /messages                 200 OK                                                                  02:07:40.264 CESTGET  /messages                 200 OK          

2024-10-24 02:07:36,244 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:36] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:38,245 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:38] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:40,265 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:40] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:42,251 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:42] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:42,450 - ERROR - Error processing message: 415 Unsupported Media Type: Did not attempt to load JSON data because the request Content-Type was not 'application/json'.
2024-10-24 02:07:42,451 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:42] "POST /message HTTP/1.1" 500 -
2024-10-24 02:07:44,266 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:44] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:45,400 - INFO - Server Garry's Mod disconnected. Remaining servers: 0
2024-10-24 02:07:45,400 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:45] "POST /disconnect HTTP/1.1" 200 -
2024-10-24 02:07:53,852 - INFO - Server Garry's Mod connected. Total connected servers: 1
2024-10-24 02:07:53,852 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:53] "POST /connect HTTP/1.1" 200 -
2024-10-24 02:07:56,037 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:56] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:56,530 - ERROR - Error processing message: 415 Unsupported Media Type: Did not attempt to load JSON data because the request Content-Type was not 'application/json'.
2024-10-24 02:07:56,530 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:56] "POST /message HTTP/1.1" 500 -
2024-10-24 02:07:58,037 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:58] "GET /messages?after=0 HTTP/1.1" 200 -
2024-10-24 02:07:59,734 - INFO - Server Garry's Mod disconnected. Remaining servers: 0
2024-10-24 02:07:59,735 - INFO - 127.0.0.1 - - [24/Oct/2024 02:07:59] "POST /disconnect HTTP/1.1" 200 -

fix all errors