using GEUTEBRUECK.GeViScope.Wrapper.DBI;
using GEUTEBRUECK.GeViScope.Wrapper.Actions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.SystemActions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.DigitalContactsActions;
using GEUTEBRUECK.GeViScope.Wrapper.Actions.ActionDispatcher;
using GEUTEBRUECK.GeViScope.Wrapper.MediaPlayer;
using Microsoft.OpenApi.Models;
using System.Collections;
using System.Collections.Concurrent;
using System.Net.WebSockets;
using System.Text;
using System.Text.Json;

var builder = WebApplication.CreateBuilder(args);

// Add CORS for web app access
builder.Services.AddCors(options =>
{
    options.AddDefaultPolicy(policy =>
    {
        policy.AllowAnyOrigin()
              .AllowAnyMethod()
              .AllowAnyHeader();
    });
});

// Add Swagger services
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "GeViScope Bridge API",
        Version = "v1",
        Description = "REST API bridge for Geutebruck GeViScope Camera Server SDK. Provides access to camera control, video routing, PTZ, and action/event handling."
    });
});

// GeViScope connection state
GscServer? gscServer = null;
GscPLCWrapper? gscPLC = null;
GscActionDispatcher? actionDispatcher = null;
string? currentAddress = null;
string? currentUsername = null;
DateTime? connectedAt = null;
int eventCount = 0;
List<string> receivedMessages = new List<string>();
List<MediaChannelInfo> mediaChannels = new List<MediaChannelInfo>();

// WebSocket clients for event streaming
ConcurrentDictionary<string, WebSocket> wsClients = new ConcurrentDictionary<string, WebSocket>();

// Monitor state tracking (populated from ViewerConnected/ViewerCleared events)
ConcurrentDictionary<int, MonitorState> monitorStates = new ConcurrentDictionary<int, MonitorState>();

// Alarm state tracking (populated from EventStarted/EventStopped events)
ConcurrentDictionary<long, AlarmState> alarmStates = new ConcurrentDictionary<long, AlarmState>();

// Update monitor state from event
void UpdateMonitorState(string actionName, int viewer, int channel = 0, int playMode = 0)
{
    if (actionName == "ViewerConnected" || actionName == "ViewerSelectionChanged")
    {
        monitorStates[viewer] = new MonitorState
        {
            ViewerId = viewer,
            CurrentChannel = channel,
            PlayMode = playMode,
            LastUpdated = DateTime.UtcNow
        };
    }
    else if (actionName == "ViewerCleared")
    {
        monitorStates[viewer] = new MonitorState
        {
            ViewerId = viewer,
            CurrentChannel = 0,
            PlayMode = 0,
            LastUpdated = DateTime.UtcNow
        };
    }
}

// Update alarm state from event
void UpdateAlarmState(string eventType, long eventId, string eventName, long typeId, long foreignKey)
{
    if (eventType == "EventStarted")
    {
        alarmStates[eventId] = new AlarmState
        {
            EventId = eventId,
            EventName = eventName,
            TypeId = typeId,
            ForeignKey = foreignKey,
            StartedAt = DateTime.UtcNow,
            IsActive = true
        };
    }
    else if (eventType == "EventStopped")
    {
        if (alarmStates.TryGetValue(eventId, out var alarm))
        {
            alarm.IsActive = false;
            alarm.StoppedAt = DateTime.UtcNow;
        }
    }
}

// Broadcast event to all connected WebSocket clients
async Task BroadcastEvent(object eventData)
{
    var json = JsonSerializer.Serialize(eventData);
    var buffer = Encoding.UTF8.GetBytes(json);
    var segment = new ArraySegment<byte>(buffer);

    var deadClients = new List<string>();

    foreach (var (clientId, ws) in wsClients)
    {
        if (ws.State == WebSocketState.Open)
        {
            try
            {
                await ws.SendAsync(segment, WebSocketMessageType.Text, true, CancellationToken.None);
            }
            catch
            {
                deadClients.Add(clientId);
            }
        }
        else
        {
            deadClients.Add(clientId);
        }
    }

    // Clean up dead connections
    foreach (var clientId in deadClients)
    {
        wsClients.TryRemove(clientId, out _);
    }
}

// Parse action string into structured event (e.g., "ViewerConnected(5, 101, 11)" -> {action: "ViewerConnected", params: {...}})
object? ParseActionToEvent(string actionStr)
{
    // Match pattern: ActionName(params)
    var match = System.Text.RegularExpressions.Regex.Match(actionStr, @"(\w+)\(([^)]*)\)");
    if (!match.Success) return null;

    var actionName = match.Groups[1].Value;
    var paramsStr = match.Groups[2].Value;
    var paramValues = paramsStr.Split(',').Select(p => p.Trim().Trim('"')).ToArray();

    // Update local state tracking based on action type
    if (actionName == "ViewerConnected" && paramValues.Length >= 3)
    {
        int.TryParse(paramValues[0], out var viewer);
        int.TryParse(paramValues[1], out var channel);
        int.TryParse(paramValues[2], out var playMode);
        UpdateMonitorState(actionName, viewer, channel, playMode);
    }
    else if (actionName == "ViewerCleared" && paramValues.Length >= 1)
    {
        int.TryParse(paramValues[0], out var viewer);
        UpdateMonitorState(actionName, viewer);
    }
    else if (actionName == "ViewerSelectionChanged" && paramValues.Length >= 3)
    {
        int.TryParse(paramValues[0], out var viewer);
        int.TryParse(paramValues[1], out var channel);
        int.TryParse(paramValues[2], out var playMode);
        UpdateMonitorState(actionName, viewer, channel, playMode);
    }

    return actionName switch
    {
        "ViewerConnected" when paramValues.Length >= 3 => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = "ViewerConnected",
            @params = new
            {
                Viewer = int.TryParse(paramValues[0], out var v) ? v : 0,
                Channel = int.TryParse(paramValues[1], out var c) ? c : 0,
                PlayMode = int.TryParse(paramValues[2], out var pm) ? pm : 0
            }
        },
        "ViewerCleared" when paramValues.Length >= 1 => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = "ViewerCleared",
            @params = new
            {
                Viewer = int.TryParse(paramValues[0], out var v) ? v : 0
            }
        },
        "ViewerSelectionChanged" when paramValues.Length >= 3 => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = "ViewerSelectionChanged",
            @params = new
            {
                Viewer = int.TryParse(paramValues[0], out var v) ? v : 0,
                Channel = int.TryParse(paramValues[1], out var c) ? c : 0,
                PlayMode = int.TryParse(paramValues[2], out var pm) ? pm : 0
            }
        },
        "DigitalInput" when paramValues.Length >= 2 => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = "DigitalInput",
            @params = new
            {
                Contact = int.TryParse(paramValues[0], out var contact) ? contact : 0,
                State = int.TryParse(paramValues[1], out var state) ? state : 0
            }
        },
        "VCAlarmQueueNotification" when paramValues.Length >= 4 => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = "VCAlarmQueueNotification",
            @params = new
            {
                Viewer = int.TryParse(paramValues[0], out var v) ? v : 0,
                Notification = int.TryParse(paramValues[1], out var n) ? n : 0,
                AlarmID = int.TryParse(paramValues[2], out var aid) ? aid : 0,
                TypeID = int.TryParse(paramValues[3], out var tid) ? tid : 0
            }
        },
        _ => new
        {
            timestamp = DateTime.UtcNow.ToString("o"),
            server = currentAddress,
            action = actionName,
            raw = actionStr
        }
    };
}

// PlayMode string to numeric value mapping (PlcViewerPlayMode enum)
var playModeMap = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase)
{
    { "unknown", 0 },
    { "play stop", 1 },
    { "play forward", 2 },
    { "play backward", 3 },
    { "fast forward", 4 },
    { "fast backward", 5 },
    { "step forward", 6 },
    { "step backward", 7 },
    { "play BOD", 8 },
    { "play EOD", 9 },
    { "quasi live", 10 },
    { "live", 11 },
    { "next event", 12 },
    { "prev event", 13 },
    { "peek live picture", 14 },
    { "next detected motion", 17 },
    { "prev detected motion", 18 }
};

// Helper function to get numeric PlayMode value
int GetPlayModeValue(string playMode)
{
    if (playModeMap.TryGetValue(playMode, out int value))
        return value;
    // Try parsing as numeric value if not found in map
    if (int.TryParse(playMode, out int numValue))
        return numValue;
    // Default to play stop (1) if unknown
    Console.WriteLine($"Unknown PlayMode '{playMode}', defaulting to 'play stop' (1)");
    return 1;
}

var app = builder.Build();

// Enable CORS
app.UseCors();

// Action dispatcher event handlers
void OnCustomAction(object? sender, GscAct_CustomActionEventArgs e)
{
    var msg = $"[{DateTime.Now:HH:mm:ss}] CustomAction({e.aInt}, \"{e.aString}\")";
    Console.WriteLine(msg);
    receivedMessages.Add(msg);
}

void OnDigitalInput(object? sender, GscAct_DigitalInputEventArgs e)
{
    var msg = $"[{DateTime.Now:HH:mm:ss}] DigitalInput(GlobalNo={e.aContact.GlobalNo})";
    Console.WriteLine(msg);
    receivedMessages.Add(msg);
}

// PLC callback handler
void OnPLCCallback(object? sender, PLCCallbackEventArgs e)
{
    try
    {
        eventCount++;

        if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnNewActionData)
        {
            var action = e.PlcNotification.GetAction();
            if (action != null && actionDispatcher != null)
            {
                var actionStr = action.ToString() ?? "";

                if (!actionDispatcher.Dispatch(action))
                {
                    var msg = $"[{DateTime.Now:HH:mm:ss}] Action: {actionStr}";
                    Console.WriteLine(msg);
                    receivedMessages.Add(msg);

                    // Broadcast to WebSocket clients
                    var eventData = ParseActionToEvent(actionStr);
                    if (eventData != null && wsClients.Count > 0)
                    {
                        _ = BroadcastEvent(eventData);
                    }
                }
            }
        }
        else if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnNewEventData)
        {
            var eventData = e.PlcNotification.GetEventData();
            if (eventData != null)
            {
                var eventType = eventData.EventNotificationType switch
                {
                    GscPlcEventNotificationType.plcenEventStarted => "started",
                    GscPlcEventNotificationType.plcenEventStopped => "stopped",
                    GscPlcEventNotificationType.plcenEventRetriggered => "retriggered",
                    _ => "unknown"
                };
                var msg = $"[{DateTime.Now:HH:mm:ss}] Event: {eventData.EventHeader.EventName} {eventData.EventHeader.EventID} {eventType}";
                Console.WriteLine(msg);
                receivedMessages.Add(msg);

                // Update local alarm state tracking
                var actionType = eventType == "started" ? "EventStarted" : eventType == "stopped" ? "EventStopped" : "EventRetriggered";
                UpdateAlarmState(actionType, eventData.EventHeader.EventID, eventData.EventHeader.EventName ?? "",
                    eventData.EventHeader.EventTypeID, Convert.ToInt64(eventData.EventHeader.ForeignKey));

                // Broadcast event notifications to WebSocket clients
                var wsEvent = new
                {
                    timestamp = DateTime.UtcNow.ToString("o"),
                    server = currentAddress,
                    action = actionType,
                    @params = new
                    {
                        EventID = eventData.EventHeader.EventID,
                        EventName = eventData.EventHeader.EventName,
                        TypeID = eventData.EventHeader.EventTypeID,
                        ForeignKey = eventData.EventHeader.ForeignKey
                    }
                };
                if (wsClients.Count > 0)
                {
                    _ = BroadcastEvent(wsEvent);
                }
            }
        }
        else if (e.PlcNotification.GetNotificationType() == GscPlcNotificationType.plcnPushCallbackLost)
        {
            var msg = $"[{DateTime.Now:HH:mm:ss}] Connection lost!";
            Console.WriteLine(msg);
            receivedMessages.Add(msg);

            // Broadcast connection lost to WebSocket clients
            var wsEvent = new
            {
                timestamp = DateTime.UtcNow.ToString("o"),
                server = currentAddress,
                action = "ConnectionLost",
                @params = new { }
            };
            if (wsClients.Count > 0)
            {
                _ = BroadcastEvent(wsEvent);
            }
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PLC Callback error: {ex.Message}");
    }
}

// Helper function to create PLC and register callbacks
void CreatePLC()
{
    if (gscServer == null) return;

    gscPLC = gscServer.CreatePLC();
    gscPLC.PLCCallback += OnPLCCallback;
    gscPLC.OpenPushCallback();

    actionDispatcher = new GscActionDispatcher();
    actionDispatcher.OnCustomAction += OnCustomAction;
    actionDispatcher.OnDigitalInput += OnDigitalInput;

    gscPLC.SubscribeActionsAll();
    gscPLC.SubscribeEventsAll();
}

// Helper function to destroy PLC
void DestroyPLC(bool connectionLost = false)
{
    if (gscPLC != null)
    {
        if (!connectionLost)
        {
            gscPLC.UnsubscribeAll();
            gscPLC.CloseCallback();
        }

        if (actionDispatcher != null)
        {
            actionDispatcher.Dispose();
            actionDispatcher = null;
        }

        gscPLC.Dispose();
        gscPLC = null;
    }
}

// Helper function to load media channels
void LoadMediaChannels()
{
    mediaChannels.Clear();
    if (gscServer == null)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] LoadMediaChannels: gscServer is null");
        return;
    }

    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] LoadMediaChannels: Starting channel query...");
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Server connected: {gscServer.IsConnected}");

    try
    {
        var channelList = new ArrayList();
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Calling MediaPlayerHelperFunctions.QueryMediaChannelList...");
        MediaPlayerHelperFunctions.QueryMediaChannelList(gscServer, out channelList);
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] QueryMediaChannelList returned, channelList is null: {channelList == null}");

        if (channelList != null)
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Found {channelList.Count} total channels from server");

            foreach (GscMediaChannelData channel in channelList)
            {
                // Include ALL channels (both active and inactive)
                mediaChannels.Add(new MediaChannelInfo
                {
                    ChannelID = channel.ChannelID,
                    GlobalNumber = channel.GlobalNumber,
                    Name = channel.Name,
                    Description = channel.Desc,
                    IsActive = channel.IsActive
                });
                Console.WriteLine($"  - Channel {channel.ChannelID}: {channel.Name} (Active: {channel.IsActive})");
            }
        }
        else
        {
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] channelList is NULL!");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Error loading media channels: {ex.Message}");
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Stack trace: {ex.StackTrace}");
    }
}

// ============================================================================
// API ENDPOINTS
// ============================================================================

// Connection endpoint
app.MapPost("/connect", (ConnectRequest request) =>
{
    try
    {
        // Disconnect existing connection
        if (gscServer != null)
        {
            DestroyPLC();
            gscServer.Disconnect(5000);
            gscServer.Dispose();
            gscServer = null;
        }

        // Create new connection
        gscServer = new GscServer();

        // Encode password
        var encodedPassword = DBIHelperFunctions.EncodePassword(request.Password);

        // Set connection parameters
        using var connectParams = new GscServerConnectParams(request.Address, request.Username, encodedPassword);
        gscServer.SetConnectParams(connectParams);

        // Connect
        var result = gscServer.Connect();

        if (result == GscServerConnectResult.connectOk)
        {
            currentAddress = request.Address;
            currentUsername = request.Username;
            connectedAt = DateTime.UtcNow;
            eventCount = 0;

            // Create PLC for actions/events
            CreatePLC();

            // Load media channels
            LoadMediaChannels();

            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Connected to GeViScope at {request.Address}");

            return Results.Ok(new
            {
                success = true,
                message = "Connected to GeViScope",
                address = request.Address,
                username = request.Username,
                channelCount = mediaChannels.Count,
                connected_at = connectedAt
            });
        }
        else
        {
            return Results.BadRequest(new
            {
                success = false,
                error = "Connection failed",
                message = result.ToString()
            });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "Connection error",
            message = ex.Message,
            stack_trace = ex.StackTrace
        });
    }
});

// Disconnect endpoint
app.MapPost("/disconnect", () =>
{
    try
    {
        DestroyPLC();

        if (gscServer != null)
        {
            gscServer.Disconnect(5000);
            gscServer.Dispose();
            gscServer = null;
        }

        currentAddress = null;
        currentUsername = null;
        connectedAt = null;
        eventCount = 0;
        mediaChannels.Clear();

        return Results.Ok(new
        {
            success = true,
            message = "Disconnected successfully"
        });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "Disconnect error",
            message = ex.Message
        });
    }
});

// Status endpoint (enhanced for monitoring)
app.MapGet("/status", () =>
{
    return Results.Ok(new
    {
        is_connected = gscServer != null,
        address = currentAddress,
        username = currentUsername,
        channel_count = mediaChannels.Count,
        connected_at = connectedAt,
        connection_duration_sec = connectedAt.HasValue ? (int)(DateTime.UtcNow - connectedAt.Value).TotalSeconds : 0,
        event_count = eventCount,
        websocket_clients = wsClients.Count,
        plc_active = gscPLC != null
    });
});

// Health check endpoint (for load balancers)
app.MapGet("/health", () =>
{
    var isHealthy = gscServer != null && gscPLC != null;
    return isHealthy
        ? Results.Ok(new { status = "healthy", server = "geviscope-bridge" })
        : Results.Ok(new { status = "degraded", server = "geviscope-bridge", message = "Not connected to GeViScope server" });
});

// Get media channels
app.MapGet("/channels", () =>
{
    if (gscServer == null)
    {
        return Results.BadRequest(new { error = "Not connected" });
    }

    return Results.Ok(new
    {
        count = mediaChannels.Count,
        channels = mediaChannels
    });
});

// Refresh media channels
app.MapPost("/channels/refresh", () =>
{
    if (gscServer == null)
    {
        return Results.BadRequest(new { error = "Not connected" });
    }

    LoadMediaChannels();

    return Results.Ok(new
    {
        count = mediaChannels.Count,
        channels = mediaChannels
    });
});

// Get current monitor states (tracked from ViewerConnected/ViewerCleared events)
app.MapGet("/monitors", () =>
{
    return Results.Ok(new
    {
        server = currentAddress,
        count = monitorStates.Count,
        monitors = monitorStates.Values.OrderBy(m => m.ViewerId).Select(m => new
        {
            viewer_id = m.ViewerId,
            current_channel = m.CurrentChannel,
            play_mode = m.PlayMode,
            last_updated = m.LastUpdated
        }),
        note = "State is tracked from ViewerConnected/ViewerCleared events. Initial state requires event activity or server query."
    });
});

// Get specific monitor state
app.MapGet("/monitors/{viewerId}", (int viewerId) =>
{
    if (monitorStates.TryGetValue(viewerId, out var state))
    {
        return Results.Ok(new
        {
            viewer_id = state.ViewerId,
            current_channel = state.CurrentChannel,
            play_mode = state.PlayMode,
            last_updated = state.LastUpdated
        });
    }
    return Results.NotFound(new { error = "Monitor not found or no events received yet" });
});

// Get active alarms (tracked from EventStarted/EventStopped notifications)
app.MapGet("/alarms/active", () =>
{
    var activeAlarms = alarmStates.Values.Where(a => a.IsActive).OrderBy(a => a.StartedAt);
    return Results.Ok(new
    {
        server = currentAddress,
        count = activeAlarms.Count(),
        alarms = activeAlarms.Select(a => new
        {
            event_id = a.EventId,
            event_name = a.EventName,
            type_id = a.TypeId,
            foreign_key = a.ForeignKey,
            started_at = a.StartedAt
        }),
        note = "Alarms tracked from EventStarted/EventStopped notifications. For complete alarm state on startup, query GeViServer bridge."
    });
});

// Get all alarms (including stopped)
app.MapGet("/alarms", () =>
{
    return Results.Ok(new
    {
        server = currentAddress,
        total = alarmStates.Count,
        active = alarmStates.Values.Count(a => a.IsActive),
        alarms = alarmStates.Values.OrderByDescending(a => a.StartedAt).Select(a => new
        {
            event_id = a.EventId,
            event_name = a.EventName,
            type_id = a.TypeId,
            foreign_key = a.ForeignKey,
            started_at = a.StartedAt,
            stopped_at = a.StoppedAt,
            is_active = a.IsActive
        })
    });
});

// Send action (generic)
app.MapPost("/action", (ActionRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Sending action: {request.Action}");

        var action = GscAction.Decode(request.Action);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {request.Action}";
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "Action sent",
                action = request.Action
            });
        }
        else
        {
            return Results.BadRequest(new
            {
                success = false,
                error = "Invalid action",
                message = "Could not decode action string"
            });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "Action error",
            message = ex.Message
        });
    }
});

// Send CustomAction
app.MapPost("/custom-action", (CustomActionRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var action = new GscAct_CustomAction(request.TypeId, request.Text ?? "");
        gscPLC.SendAction(action);
        action.Dispose();

        var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: CustomAction({request.TypeId}, \"{request.Text}\")";
        receivedMessages.Add(logMsg);

        return Results.Ok(new
        {
            success = true,
            message = "CustomAction sent",
            type_id = request.TypeId,
            text = request.Text
        });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "CustomAction error",
            message = ex.Message
        });
    }
});

// ViewerConnect - connect a viewer to a channel with play mode
app.MapPost("/viewer/connect", (ViewerConnectRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var playModeValue = GetPlayModeValue(request.PlayMode);
        var actionStr = $"ViewerConnect({request.Viewer}, {request.Channel}, {playModeValue})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            Console.WriteLine(logMsg);
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerConnect sent",
                viewer = request.Viewer,
                channel = request.Channel,
                play_mode = request.PlayMode
            });
        }
        else
        {
            return Results.BadRequest(new { success = false, error = "Failed to create ViewerConnect action" });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerConnect error",
            message = ex.Message
        });
    }
});

// ViewerConnectLive - connect a viewer to a channel (GeViScope equivalent of CrossSwitch)
app.MapPost("/viewer/connect-live", (ViewerConnectLiveRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"ViewerConnectLive({request.Viewer}, {request.Channel})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            Console.WriteLine(logMsg);
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerConnectLive sent",
                viewer = request.Viewer,
                channel = request.Channel
            });
        }
        else
        {
            return Results.BadRequest(new { success = false, error = "Failed to create ViewerConnectLive action" });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerConnectLive error",
            message = ex.Message
        });
    }
});

// ViewerClear - clear viewer display
app.MapPost("/viewer/clear", (ViewerClearRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"ViewerClear({request.Viewer})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            Console.WriteLine(logMsg);
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerClear sent",
                viewer = request.Viewer
            });
        }
        else
        {
            return Results.BadRequest(new { success = false, error = "Failed to create ViewerClear action" });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerClear error",
            message = ex.Message
        });
    }
});

// CrossSwitch - redirects to ViewerConnectLive for GeViScope (for backward compatibility)
app.MapPost("/crossswitch", (CrossSwitchRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // For GeViScope, use ViewerConnectLive instead of CrossSwitch
        // VideoOutput = Viewer, VideoInput = Channel
        var actionStr = $"ViewerConnectLive({request.VideoOutput}, {request.VideoInput})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr} (via /crossswitch)";
            Console.WriteLine(logMsg);
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerConnectLive sent (CrossSwitch mapped)",
                viewer = request.VideoOutput,
                channel = request.VideoInput
            });
        }
        else
        {
            return Results.BadRequest(new { success = false, error = "Failed to create ViewerConnectLive action" });
        }
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "CrossSwitch error",
            message = ex.Message
        });
    }
});

// PTZ Camera Control - Pan
app.MapPost("/camera/pan", (CameraPanRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // Correct action names: PanLeft, PanRight (not CameraPanLeft/Right)
        var direction = request.Direction.ToLower() == "left" ? "Left" : "Right";
        var actionStr = $"Pan{direction}({request.Camera}, {request.Speed})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ Pan: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = $"Camera pan {direction}",
                camera = request.Camera,
                speed = request.Speed
            });
        }

        return Results.BadRequest(new { error = "Failed to create pan action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Tilt
app.MapPost("/camera/tilt", (CameraTiltRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // Correct action names: TiltUp, TiltDown (not CameraTiltUp/Down)
        var direction = request.Direction.ToLower() == "up" ? "Up" : "Down";
        var actionStr = $"Tilt{direction}({request.Camera}, {request.Speed})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ Tilt: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = $"Camera tilt {direction}",
                camera = request.Camera,
                speed = request.Speed
            });
        }

        return Results.BadRequest(new { error = "Failed to create tilt action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Zoom
app.MapPost("/camera/zoom", (CameraZoomRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // Correct action names: ZoomIn, ZoomOut (not CameraZoomIn/Out)
        var direction = request.Direction.ToLower() == "in" ? "In" : "Out";
        var actionStr = $"Zoom{direction}({request.Camera}, {request.Speed})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ Zoom: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = $"Camera zoom {direction}",
                camera = request.Camera,
                speed = request.Speed
            });
        }

        return Results.BadRequest(new { error = "Failed to create zoom action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Stop all movement
app.MapPost("/camera/stop", (CameraStopRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // CameraStopAll is the correct action name
        var actionStr = $"CameraStopAll({request.Camera})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ Stop: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Camera stopped",
                camera = request.Camera
            });
        }

        return Results.BadRequest(new { error = "Failed to create stop action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Stop pan movement
app.MapPost("/camera/pan-stop", (CameraStopRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"PanStop({request.Camera})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ PanStop: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Pan stopped",
                camera = request.Camera
            });
        }

        return Results.BadRequest(new { error = "Failed to create pan stop action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Stop tilt movement
app.MapPost("/camera/tilt-stop", (CameraStopRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"TiltStop({request.Camera})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ TiltStop: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Tilt stopped",
                camera = request.Camera
            });
        }

        return Results.BadRequest(new { error = "Failed to create tilt stop action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Stop zoom movement
app.MapPost("/camera/zoom-stop", (CameraStopRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"ZoomStop({request.Camera})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ ZoomStop: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Zoom stopped",
                camera = request.Camera
            });
        }

        return Results.BadRequest(new { error = "Failed to create zoom stop action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// PTZ Camera Control - Go to preset
app.MapPost("/camera/preset", (CameraPresetRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        // Correct action name: PrePosCallUp (not CameraGotoPreset)
        var actionStr = $"PrePosCallUp({request.Camera}, {request.Preset})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] PTZ Preset: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = $"Camera going to preset {request.Preset}",
                camera = request.Camera,
                preset = request.Preset
            });
        }

        return Results.BadRequest(new { error = "Failed to create preset action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// Digital Output - Close contact
app.MapPost("/digital-io/close", (DigitalContactRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"CloseDigitalOutput({request.ContactId})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Digital output closed",
                contact_id = request.ContactId
            });
        }

        return Results.BadRequest(new { error = "Failed to create action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// Digital Output - Open contact
app.MapPost("/digital-io/open", (DigitalContactRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var actionStr = $"OpenDigitalOutput({request.ContactId})";
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            return Results.Ok(new
            {
                success = true,
                message = "Digital output opened",
                contact_id = request.ContactId
            });
        }

        return Results.BadRequest(new { error = "Failed to create action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new { error = ex.Message });
    }
});

// ============================================================================
// PLAYBACK CONTROL ENDPOINTS
// ============================================================================

// ViewerSetPlayMode - Set playback mode and speed
// PlayMode values: "step forward", "step backward", "fast forward", "fast backward",
//                  "play forward", "play backward", "play stop", "live", "play BOD", "play EOD"
app.MapPost("/viewer/set-play-mode", (ViewerSetPlayModeRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var playModeValue = GetPlayModeValue(request.PlayMode);
        var actionStr = $"ViewerSetPlayMode({request.Viewer}, {playModeValue}, {request.PlaySpeed})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Playback: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerSetPlayMode sent",
                viewer = request.Viewer,
                play_mode = request.PlayMode,
                play_speed = request.PlaySpeed
            });
        }

        return Results.BadRequest(new { success = false, error = "Failed to create ViewerSetPlayMode action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerSetPlayMode error",
            message = ex.Message
        });
    }
});

// ViewerPlayFromTime - Go to specific date/time
// Time format: "2024/01/15 14:30:00,000 GMT+01:00"
app.MapPost("/viewer/play-from-time", (ViewerPlayFromTimeRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var playModeValue = GetPlayModeValue(request.PlayMode);
        var actionStr = $"ViewerPlayFromTime({request.Viewer}, {request.Channel}, {playModeValue}, \"{request.Time}\")";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Playback: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerPlayFromTime sent",
                viewer = request.Viewer,
                channel = request.Channel,
                play_mode = request.PlayMode,
                time = request.Time
            });
        }

        return Results.BadRequest(new { success = false, error = "Failed to create ViewerPlayFromTime action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerPlayFromTime error",
            message = ex.Message
        });
    }
});

// ViewerJumpByTime - Jump playback position by seconds
app.MapPost("/viewer/jump-by-time", (ViewerJumpByTimeRequest request) =>
{
    try
    {
        if (gscServer == null || gscPLC == null)
        {
            return Results.BadRequest(new { error = "Not connected" });
        }

        var playModeValue = GetPlayModeValue(request.PlayMode);
        var actionStr = $"ViewerJumpByTime({request.Viewer}, {request.Channel}, {playModeValue}, {request.TimeInSec})";
        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Playback: {actionStr}");
        var action = GscAction.Decode(actionStr);
        if (action != null)
        {
            gscPLC.SendAction(action);
            action.Dispose();

            var logMsg = $"[{DateTime.Now:HH:mm:ss}] SENT: {actionStr}";
            receivedMessages.Add(logMsg);

            return Results.Ok(new
            {
                success = true,
                message = "ViewerJumpByTime sent",
                viewer = request.Viewer,
                channel = request.Channel,
                play_mode = request.PlayMode,
                time_in_sec = request.TimeInSec
            });
        }

        return Results.BadRequest(new { success = false, error = "Failed to create ViewerJumpByTime action" });
    }
    catch (Exception ex)
    {
        return Results.BadRequest(new
        {
            success = false,
            error = "ViewerJumpByTime error",
            message = ex.Message
        });
    }
});

// DEBUG: Test action decoding - helps identify which actions are supported by SDK
app.MapPost("/debug/test-action", (ActionRequest request) =>
{
    var results = new List<object>();

    // Test the provided action
    var action = GscAction.Decode(request.Action);
    results.Add(new {
        action_string = request.Action,
        decoded = action != null,
        action_type = action?.ToString() ?? "null"
    });

    if (action != null)
    {
        action.Dispose();
    }

    return Results.Ok(new {
        test_results = results,
        note = "If decoded=true, the action can be sent via PLC"
    });
});

// DEBUG: Test all playback action formats
app.MapGet("/debug/test-playback-actions", () =>
{
    var testActions = new[] {
        // Numeric PlayMode tests
        "ViewerConnect(1001, 1, 1)",
        "ViewerConnect(1001, 1, 6)",
        "ViewerSetPlayMode(1001, 1, 1)",
        "ViewerSetPlayMode(1001, 6, 1)",
        "ViewerPlayFromTime(1001, 1, 2, \"2024/01/15 14:30:00,000 GMT+01:00\")",
        "ViewerJumpByTime(1001, 1, 2, 60)",
        // String PlayMode tests (old format)
        "ViewerConnect(1001, 1, \"play stop\")",
        "ViewerSetPlayMode(1001, \"step forward\", 1)",
        // Working actions for comparison
        "ViewerConnectLive(1001, 1)",
        "ViewerClear(1001)",
        "CustomAction(1, \"test\")",
        "PanLeft(1, 50)"
    };

    var results = new List<object>();
    foreach (var actionStr in testActions)
    {
        var action = GscAction.Decode(actionStr);
        results.Add(new {
            action_string = actionStr,
            decoded = action != null,
            can_send = action != null
        });
        if (action != null)
        {
            action.Dispose();
        }
    }

    return Results.Ok(new {
        test_results = results,
        summary = $"Decoded: {results.Count(r => ((dynamic)r).decoded)} / {results.Count}"
    });
});

// Get message log
app.MapGet("/messages", () =>
{
    return Results.Ok(new
    {
        count = receivedMessages.Count,
        messages = receivedMessages.TakeLast(100).ToList()
    });
});

// Clear message log
app.MapPost("/messages/clear", () =>
{
    receivedMessages.Clear();
    return Results.Ok(new { message = "Message log cleared" });
});

// Enable WebSocket support
app.UseWebSockets();

// WebSocket endpoint for event streaming
app.Map("/ws/events", async context =>
{
    if (context.WebSockets.IsWebSocketRequest)
    {
        var ws = await context.WebSockets.AcceptWebSocketAsync();
        var clientId = Guid.NewGuid().ToString();
        wsClients[clientId] = ws;

        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] WebSocket client connected: {clientId}");

        // Send initial connection confirmation
        var welcome = JsonSerializer.Serialize(new
        {
            type = "connected",
            clientId = clientId,
            server = currentAddress,
            message = "Connected to GeViScope Bridge event stream"
        });
        await ws.SendAsync(
            new ArraySegment<byte>(Encoding.UTF8.GetBytes(welcome)),
            WebSocketMessageType.Text,
            true,
            CancellationToken.None
        );

        // Keep connection open and handle incoming messages (ping/pong, close)
        var buffer = new byte[1024];
        try
        {
            while (ws.State == WebSocketState.Open)
            {
                var result = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);

                if (result.MessageType == WebSocketMessageType.Close)
                {
                    await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Client requested close", CancellationToken.None);
                    break;
                }
            }
        }
        catch (WebSocketException)
        {
            // Client disconnected
        }
        finally
        {
            wsClients.TryRemove(clientId, out _);
            Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] WebSocket client disconnected: {clientId}");
        }
    }
    else
    {
        context.Response.StatusCode = 400;
    }
});

Console.WriteLine("========================================");
Console.WriteLine("GeViScope Bridge starting on port 7720");
Console.WriteLine("WebSocket events: ws://localhost:7720/ws/events");
Console.WriteLine("========================================");

app.Run("http://localhost:7720");

// Request/Response Models
record ConnectRequest(string Address, string Username, string Password);
record ActionRequest(string Action);
record CustomActionRequest(int TypeId, string? Text);
record ViewerConnectRequest(int Viewer, int Channel, string PlayMode = "live");
record ViewerConnectLiveRequest(int Viewer, int Channel);
record ViewerClearRequest(int Viewer);
record CrossSwitchRequest(int VideoInput, int VideoOutput, int SwitchMode = 0);
record CameraPanRequest(int Camera, string Direction, int Speed = 50);
record CameraTiltRequest(int Camera, string Direction, int Speed = 50);
record CameraZoomRequest(int Camera, string Direction, int Speed = 50);
record CameraStopRequest(int Camera);
record CameraPresetRequest(int Camera, int Preset);
record DigitalContactRequest(int ContactId);
record ViewerSetPlayModeRequest(int Viewer, string PlayMode, double PlaySpeed = 1.0);
record ViewerPlayFromTimeRequest(int Viewer, int Channel, string PlayMode, string Time);
record ViewerJumpByTimeRequest(int Viewer, int Channel, string PlayMode, int TimeInSec);

class MediaChannelInfo
{
    public long ChannelID { get; set; }
    public long GlobalNumber { get; set; }
    public string Name { get; set; } = "";
    public string Description { get; set; } = "";
    public bool IsActive { get; set; }
}

// Monitor state tracked from ViewerConnected/ViewerCleared events
class MonitorState
{
    public int ViewerId { get; set; }
    public int CurrentChannel { get; set; }
    public int PlayMode { get; set; }
    public DateTime LastUpdated { get; set; }
}

// Alarm state tracked from EventStarted/EventStopped notifications
class AlarmState
{
    public long EventId { get; set; }
    public string EventName { get; set; } = "";
    public long TypeId { get; set; }
    public long ForeignKey { get; set; }
    public DateTime StartedAt { get; set; }
    public DateTime? StoppedAt { get; set; }
    public bool IsActive { get; set; }
}
