/*
Webhook setup:

1. cmd as admin:
    > netsh http add urlacl url=http://+:80/ctrader user=RAKI\Brummel
    
2. Windows Defender Firewall, Outgoing Rules:
    New rule, Ports-->TCP-->local port = 80, remote ports = all

3. FritzBox:
    New "MyFritz-Freigabe", HTTP-Server, Deviceport 80, external port 80, Folder "ctrader"

Result:
Webhook in Tradingview: http://raki.1k9zmiax0jpc2iys.myfritz.net:80/ctrader
*/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
using System.Resources;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Globalization;
using System.Text.Json;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Plugins;

[Plugin(AccessRights = AccessRights.FullAccess)]
public class Webhook : Plugin
{
    private HttpListener listener;
    private string lastMsg = "";
    private bool msgProcessed = false;
    private readonly CancellationTokenSource cancellationTokenSource = new();
    private Task task;
    private TradeInfo tradeInfo;
    private Symbol symbol;
    private readonly List<Chart> charts = new();

    private class TradeInfo
    {
        public string Symbol { get; set; }
        public double StopLoss { get; set; }
        public int Risk { get; set; }
        public double Buy { get; set; }
        public double Sell { get; set; }
        public double Open { get; set; }
        public double Low { get; set; }
        public double High { get; set; }
        public double Close { get; set; }
        public DateTime ReceiveTime { get; set; }
        public List<Chart> Charts { get; set; }
    }

    protected override void OnStart()
    {      
        listener = new HttpListener();
        //listener.Prefixes.Add("http://localhost:3000/");
        listener.Prefixes.Add("http://+:80/ctrader/");
        listener.Start();

        var cancellationToken = cancellationTokenSource.Token;        

        task = Task.Run(() =>
        {
            while( !cancellationToken.IsCancellationRequested )
            {
                // Wait for a request to come in
                var context = listener.GetContext();
                var request = context.Request;

                // Read the request body
                using (var reader = new StreamReader(request.InputStream, request.ContentEncoding))
                {
                    string m = reader.ReadToEnd();

                    Monitor.Enter(this); 
                    try
                    {
                        if( m != lastMsg )
                        {
                            lastMsg = m;
                            msgProcessed = false;
                        }
                    }
                    finally
                    {
                        Monitor.Exit(this); 
                    }
                }

                // Send a response
                var response = context.Response;
                response.StatusCode = 200;
                response.ContentType = "text/plain";
                var buffer = Encoding.UTF8.GetBytes("OK - cTrader");
                response.ContentLength64 = buffer.Length;
                response.OutputStream.Write(buffer, 0, buffer.Length);
                response.OutputStream.Close(); 
            }
        }, cancellationToken);
        
        Timer.Start(TimeSpan.FromSeconds(1));
        Positions.Opened += (a) => { UpdateStops(); };
    }
    
    protected override void OnTimer()
    {
       ProcessMsg();
    }

    protected override void OnStop()
    {
        Timer.Stop();
        cancellationTokenSource.Cancel();
        task.Wait();
        listener.Stop();
    }

    protected bool UpdateTradeInfo()
    {
        string m = "";
    
        Monitor.Enter(this); 
        try
        {  
            if( !msgProcessed )
            {
                msgProcessed = true;
                m = lastMsg;
            }
        }
        finally
        {
            Monitor.Exit(this); 
        }

        if( m== "" )
            return false;

        Print( "Received: {0}", m );
        tradeInfo = JsonSerializer.Deserialize<TradeInfo>(m);
        tradeInfo.ReceiveTime = DateTime.Now;
        symbol = Symbols.GetSymbol(tradeInfo.Symbol);
        if( symbol == null )
        {
            Print( "Error: unknown symbol {0}", tradeInfo.Symbol );
            return false;
        }
        
        tradeInfo.StopLoss = Math.Round( tradeInfo.StopLoss, symbol.Digits );
        tradeInfo.Buy = Math.Round( tradeInfo.Buy, symbol.Digits );
        tradeInfo.Sell = Math.Round( tradeInfo.Sell, symbol.Digits );

        charts.Clear();
        foreach (var frame in ChartManager)
            if (frame is ChartFrame chartFrame) 
                if( chartFrame.Chart.Symbol == symbol )
                    charts.Add( chartFrame.Chart );

        foreach (var chart in charts )
            chart.DrawStaticText("Message", tradeInfo.ReceiveTime.ToString("HH:mm:ss")+ ": "+m, VerticalAlignment.Top, HorizontalAlignment.Right, chart.ColorSettings.ForegroundColor);

        return true;
    }

    protected void ProcessMsg()
    {
        if( UpdateTradeInfo() )
        {
           foreach (var chart in charts )
           {
               chart.DrawHorizontalLine( "SL", tradeInfo.StopLoss, Color.White );
               if( tradeInfo.Buy > 0 )
                   chart.DrawIcon("Buy" + chart.Bars.Count, ChartIconType.UpArrow, chart.Bars.Count - 1, chart.Bars.ClosePrices[chart.Bars.Count - 1], Color.Green);
                if( tradeInfo.Sell > 0 )
                   chart.DrawIcon("Sell" + chart.Bars.Count, ChartIconType.DownArrow, chart.Bars.Count - 1, chart.Bars.ClosePrices[chart.Bars.Count - 1], Color.Red);
            }
            
            ManageOrders();
            UpdateStops();
        }
    }

    private PendingOrder pendingOrder;

    private double CalculateVolume(double StopLoss)
    {
        var maxAmountRisked = Account.Equity * ((double)tradeInfo.Risk / 100);
        return symbol.NormalizeVolumeInUnits(maxAmountRisked / (StopLoss/symbol.PipSize * symbol.PipValue), RoundingMode.Down);
                        
    }

    protected void ManageOrders()
    {
    return;

        if( Positions.Count > 0 || tradeInfo.Symbol != "GER40" )
            return;

        if( tradeInfo.Buy > 0 )
        {
            double buyPrice = Math.Round( tradeInfo.Buy+symbol.Spread, symbol.Digits );
            double stopLossPrice = Math.Round( tradeInfo.StopLoss-symbol.Spread, symbol.Digits );

            if( pendingOrder != null )
                if( pendingOrder.TradeType != TradeType.Buy || pendingOrder.TargetPrice != buyPrice )
                {
                    pendingOrder.Cancel();
                    pendingOrder = null;
                }

            if( pendingOrder == null )
            {
                TradeResult tradeResult = PlaceStopOrder(
                    TradeType.Buy, tradeInfo.Symbol, 
                    2, // CalculateVolume( buyPrice-stopLossPrice ), 
                    buyPrice, "Managed long",
                    (buyPrice-stopLossPrice)/symbol.PipSize, null);

                if( tradeResult.IsSuccessful )
                    pendingOrder = tradeResult.PendingOrder;
            }
        }
        else if( tradeInfo.Sell > 0 )
        {
            double sellPrice = Math.Round( tradeInfo.Sell-symbol.Spread, symbol.Digits );
            double stopLossPrice = Math.Round( tradeInfo.StopLoss+symbol.Spread, symbol.Digits );

            if( pendingOrder != null )
                if( pendingOrder.TradeType != TradeType.Sell || pendingOrder.TargetPrice != sellPrice )
                {
                    pendingOrder.Cancel();
                    pendingOrder = null;
                }

            if( pendingOrder == null )
            {
                TradeResult tradeResult = PlaceStopOrder(
                    TradeType.Sell, tradeInfo.Symbol, 
                    2, // CalculateVolume( stopLossPrice-sellPrice ), 
                    sellPrice, "Managed short",
                    (stopLossPrice-sellPrice)/symbol.PipSize, null);

                if( tradeResult.IsSuccessful )
                    pendingOrder = tradeResult.PendingOrder;
            }
        }        
    }

    protected void UpdateStops()
    {
        foreach (var position in Positions)
        {
            if (position.Symbol != symbol)
                continue; 

            if( position.TradeType == TradeType.Buy )
            {
                double sl = Math.Round( tradeInfo.StopLoss-symbol.Spread, symbol.Digits );

                if( !position.StopLoss.HasValue || position.StopLoss < sl )
                    ModifyPosition(position, sl, position.TakeProfit);
/*
                if( tradeInfo.Close < sl )
                {
                    position.Close();
                    continue; 
                }

                double be = Math.Round( position.EntryPrice+symbol.Spread, symbol.Digits );
                double sl_be = Math.Min( sl, be );

                if( !position.StopLoss.HasValue || position.StopLoss < sl_be )
                    ModifyPosition(position, sl_be, position.TakeProfit);
*/
}
            else
            {
                double sl = Math.Round( tradeInfo.StopLoss+symbol.Spread, symbol.Digits );

                if( !position.StopLoss.HasValue || position.StopLoss > sl )
                    ModifyPosition(position, sl, position.TakeProfit);
/*                    
                if( tradeInfo.Close > sl )
                {
                    position.Close();
                    continue; 
                }

                double be = Math.Round( position.EntryPrice-symbol.Spread, symbol.Digits );
                double sl_be = Math.Max( sl, be );

                if( !position.StopLoss.HasValue || position.StopLoss > sl_be )
                    ModifyPosition(position, sl_be, position.TakeProfit);
*/
            }                    
        }
    }
}