using System.ComponentModel.DataAnnotations;
using Managing.Api.Models.Responses;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Hubs;
using Managing.Application.ManageBot.Commands;
using Managing.Common;
using Managing.Domain.Bots;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Trades;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
using static Managing.Common.Enums;

namespace Managing.Api.Controllers;

/// <summary>
/// Controller for bot operations such as starting, stopping, deleting, and managing bots.
/// Requires authorization for access and produces JSON responses.
/// </summary>
[ApiController]
[Authorize]
[Route("[controller]")]
[Produces("application/json")]
public class BotController : BaseController
{
    private readonly IMediator _mediator;
    private readonly ILogger<BotController> _logger;
    private readonly IHubContext<BotHub> _hubContext;
    private readonly IBacktester _backtester;
    private readonly IBotService _botService;
    private readonly IAccountService _accountService;
    private readonly IMoneyManagementService _moneyManagementService;

    /// <summary>
    /// Initializes a new instance of the <see cref="BotController"/> class.
    /// </summary>
    /// <param name="logger">Logger for logging information.</param>
    /// <param name="mediator">Mediator for handling commands and requests.</param>
    /// <param name="hubContext">SignalR hub context for real-time communication.</param>
    /// <param name="backtester">Backtester for running backtests on bots.</param>
    public BotController(ILogger<BotController> logger, IMediator mediator, IHubContext<BotHub> hubContext,
        IBacktester backtester, IBotService botService, IUserService userService,
        IAccountService accountService, IMoneyManagementService moneyManagementService) : base(userService)
    {
        _logger = logger;
        _mediator = mediator;
        _hubContext = hubContext;
        _backtester = backtester;
        _botService = botService;
        _accountService = accountService;
        _moneyManagementService = moneyManagementService;
    }

    /// <summary>
    /// Checks if the current authenticated user owns the account associated with the specified bot or account name
    /// </summary>
    /// <param name="identifier">The identifier of the bot to check</param>
    /// <param name="accountName">Optional account name to check when creating a new bot</param>
    /// <returns>True if the user owns the account, False otherwise</returns>
    private async Task<bool> UserOwnsBotAccount(string identifier, string accountName = null)
    {
        try
        {
            var user = await GetUser();
            if (user == null)
                return false;

            // For new bot creation, check if the user owns the account provided in the request
            if (!string.IsNullOrEmpty(accountName))
            {
                var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
                var account = await accountService.GetAccount(accountName, true, false);
                // Compare the user names
                return account != null && account.User != null && account.User.Name == user.Name;
            }

            // For existing bots, check if the user owns the bot's account
            var activeBots = _botService.GetActiveBots();
            var bot = activeBots.FirstOrDefault(b => b.Identifier == identifier);
            if (bot == null)
                return true; // Bot doesn't exist yet, so no ownership conflict

            var botAccountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();
            var botAccount = await botAccountService.GetAccount(bot.Config.AccountName, true, false);
            // Compare the user names
            return botAccount != null && botAccount.User != null && botAccount.User.Name == user.Name;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error checking if user owns bot account");
            return false;
        }
    }

    /// <summary>
    /// Starts a bot with the specified parameters.
    /// </summary>
    /// <param name="request">The request containing bot start parameters.</param>
    /// <returns>A string indicating the result of the start operation.</returns>
    [HttpPost]
    [Route("Start")]
    public async Task<ActionResult<string>> Start(StartBotRequest request)
    {
        try
        {
            if (request.Config == null)
            {
                return BadRequest("Bot configuration is required");
            }

            // Check if user owns the account specified in the request
            if (!await UserOwnsBotAccount(null, request.Config.AccountName))
            {
                return Forbid("You don't have permission to start a bot with this account");
            }

            // Trigger error if money management is not provided
            if (string.IsNullOrEmpty(request.MoneyManagementName) && request.Config.MoneyManagement == null)
            {
                return BadRequest("Money management name or money management object is required");
            }

            var user = await GetUser();
            
            // Get money management if name is provided
            MoneyManagement moneyManagement = request.Config.MoneyManagement;
            if (!string.IsNullOrEmpty(request.MoneyManagementName))
            {
                moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
                if (moneyManagement == null)
                {
                    return BadRequest("Money management not found");
                }
            }

            // Validate initialTradingBalance
            if (request.Config.BotTradingBalance <= Constants.GMX.Config.MinimumPositionAmount)
            {
                return BadRequest(
                    $"Initial trading balance must be greater than {Constants.GMX.Config.MinimumPositionAmount}");
            }

            // Validate cooldown period
            if (request.Config.CooldownPeriod < 1)
            {
                return BadRequest("Cooldown period must be at least 1 candle");
            }

            // Validate max loss streak
            if (request.Config.MaxLossStreak < 0)
            {
                return BadRequest("Max loss streak cannot be negative");
            }

            // Validate max position time hours
            if (request.Config.MaxPositionTimeHours.HasValue && request.Config.MaxPositionTimeHours.Value <= 0)
            {
                return BadRequest("Max position time hours must be greater than 0 if specified");
            }

            // Validate CloseEarlyWhenProfitable consistency
            if (request.Config.CloseEarlyWhenProfitable && !request.Config.MaxPositionTimeHours.HasValue)
            {
                return BadRequest("CloseEarlyWhenProfitable can only be enabled when MaxPositionTimeHours is set");
            }

            // Update the config with final money management
            var config = new TradingBotConfig
            {
                AccountName = request.Config.AccountName,
                MoneyManagement = moneyManagement,
                Ticker = request.Config.Ticker,
                ScenarioName = request.Config.ScenarioName,
                Timeframe = request.Config.Timeframe,
                IsForWatchingOnly = request.Config.IsForWatchingOnly,
                BotTradingBalance = request.Config.BotTradingBalance,
                BotType = request.Config.BotType,
                CooldownPeriod = request.Config.CooldownPeriod,
                MaxLossStreak = request.Config.MaxLossStreak,
                MaxPositionTimeHours = request.Config.MaxPositionTimeHours,
                FlipOnlyWhenInProfit = request.Config.FlipOnlyWhenInProfit,
                IsForBacktest = false,
                FlipPosition = request.Config.BotType == BotType.FlippingBot,
                Name = request.Config.Name,
                CloseEarlyWhenProfitable = request.Config.CloseEarlyWhenProfitable
            };

            var result = await _mediator.Send(new StartBotCommand(config, request.Config.Name, user));

            await NotifyBotSubscriberAsync();
            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error starting bot");
            return StatusCode(500, $"Error starting bot: {ex.Message}");
        }
    }

    /// <summary>
    /// Stops a bot specified by type and name.
    /// </summary>
    /// <param name="botType">The type of the bot to stop.</param>
    /// <param name="identifier">The identifier of the bot to stop.</param>
    /// <returns>A string indicating the result of the stop operation.</returns>
    [HttpGet]
    [Route("Stop")]
    public async Task<ActionResult<string>> Stop(BotType botType, string identifier)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(identifier))
            {
                return Forbid("You don't have permission to stop this bot");
            }

            var result = await _mediator.Send(new StopBotCommand(botType, identifier));
            _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");

            await NotifyBotSubscriberAsync();

            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error stopping bot");
            return StatusCode(500, $"Error stopping bot: {ex.Message}");
        }
    }

    /// <summary>
    /// Deletes a bot specified by name.
    /// </summary>
    /// <param name="identifier">The identifier of the bot to delete.</param>
    /// <returns>A boolean indicating the result of the delete operation.</returns>
    [HttpDelete]
    [Route("Delete")]
    public async Task<ActionResult<bool>> Delete(string identifier)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(identifier))
            {
                return Forbid("You don't have permission to delete this bot");
            }

            var result = await _botService.DeleteBot(identifier);
            await NotifyBotSubscriberAsync();
            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error deleting bot");
            return StatusCode(500, $"Error deleting bot: {ex.Message}");
        }
    }

    /// <summary>
    /// Stops all active bots.
    /// </summary>
    /// <returns>A string summarizing the results of the stop operations for all bots.</returns>
    [HttpPost("stop-all")]
    public async Task<string> StopAll()
    {
        // This method should be restricted to only stop bots owned by the current user
        var user = await GetUser();
        if (user == null)
            return "No authenticated user found";

        try
        {
            var bots = await GetBotList();
            // Filter to only include bots owned by the current user
            var userBots = new List<TradingBotResponse>();

            foreach (var bot in bots)
            {
                var account = await _accountService.GetAccount(bot.Config.AccountName, true, false);
                // Compare the user names
                if (account != null && account.User != null && account.User.Name == user.Name)
                {
                    userBots.Add(bot);
                }
            }

            foreach (var bot in userBots)
            {
                await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier));
                await _hubContext.Clients.All.SendAsync("SendNotification",
                    $"Bot {bot.Identifier} paused by {user.Name}.", "Info");
            }

            await NotifyBotSubscriberAsync();
            return "All your bots have been stopped successfully!";
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error stopping all bots");
            return $"Error stopping bots: {ex.Message}";
        }
    }

    /// <summary>
    /// Restarts a bot specified by type and name.
    /// </summary>
    /// <param name="botType">The type of the bot to restart.</param>
    /// <param name="identifier">The identifier of the bot to restart.</param>
    /// <returns>A string indicating the result of the restart operation.</returns>
    [HttpGet]
    [Route("Restart")]
    public async Task<ActionResult<string>> Restart(BotType botType, string identifier)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(identifier))
            {
                return Forbid("You don't have permission to restart this bot");
            }

            var result = await _mediator.Send(new RestartBotCommand(botType, identifier));
            _logger.LogInformation($"{botType} type with identifier {identifier} is now {result}");

            await NotifyBotSubscriberAsync();

            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error restarting bot");
            return StatusCode(500, $"Error restarting bot: {ex.Message}");
        }
    }

    /// <summary>
    /// Restarts all active bots.
    /// </summary>
    /// <returns>A string summarizing the results of the restart operations for all bots.</returns>
    [HttpPost("restart-all")]
    public async Task<string> RestartAll()
    {
        var user = await GetUser();
        if (user == null)
            return "No authenticated user found";

        try
        {
            var bots = await GetBotList();
            // Filter to only include bots owned by the current user
            var userBots = new List<TradingBotResponse>();
            var accountService = HttpContext.RequestServices.GetRequiredService<IAccountService>();

            foreach (var bot in bots)
            {
                var account = await accountService.GetAccount(bot.Config.AccountName, true, false);
                // Compare the user names
                if (account != null && account.User != null && account.User.Name == user.Name)
                {
                    userBots.Add(bot);
                }
            }

            foreach (var bot in userBots)
            {
                // We can't directly restart a bot with just BotType and Name
                // Instead, stop the bot and then retrieve the backup to start it again
                await _mediator.Send(new StopBotCommand(bot.Config.BotType, bot.Identifier));

                // Get the saved bot backup
                var backup = _botService.GetBotBackup(bot.Identifier);
                if (backup != null)
                {
                    _botService.StartBotFromBackup(backup);
                    await _hubContext.Clients.All.SendAsync("SendNotification",
                        $"Bot {bot.Identifier} restarted by {user.Name}.", "Info");
                }
            }

            await NotifyBotSubscriberAsync();
            return "All your bots have been restarted successfully!";
        }
        catch (Exception e)
        {
            _logger.LogError(e, "Failed to restart all bots");
            return $"Error restarting bots: {e.Message}";
        }
    }

    /// <summary>
    /// Toggles the watching status of a bot specified by name.
    /// </summary>
    /// <param name="identifier">The identifier of the bot to toggle watching status.</param>
    /// <returns>A string indicating the new watching status of the bot.</returns>
    [HttpGet]
    [Route("ToggleIsForWatching")]
    public async Task<ActionResult<string>> ToggleIsForWatching(string identifier)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(identifier))
            {
                return Forbid("You don't have permission to modify this bot");
            }

            var result = await _mediator.Send(new ToggleIsForWatchingCommand(identifier));
            _logger.LogInformation($"Bot with identifier {identifier} is now {result}");

            await NotifyBotSubscriberAsync();

            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error toggling bot watching status");
            return StatusCode(500, $"Error toggling bot watching status: {ex.Message}");
        }
    }

    /// <summary>
    /// Retrieves a list of active bots.
    /// </summary>
    /// <returns>A list of active trading bots.</returns>
    [HttpGet]
    public async Task<List<TradingBotResponse>> GetActiveBots()
    {
        return await GetBotList();
    }

    /// <summary>
    /// Retrieves a list of active bots by sending a command to the mediator.
    /// </summary>
    /// <returns>A list of trading bots.</returns>
    private async Task<List<TradingBotResponse>> GetBotList()
    {
        var result = await _mediator.Send(new GetActiveBotsCommand());
        var list = new List<TradingBotResponse>();

        foreach (var item in result)
        {
            list.Add(new TradingBotResponse
            {
                Status = item.GetStatus(),
                Signals = item.Signals.ToList(),
                Positions = item.Positions,
                Candles = item.Candles.DistinctBy(c => c.Date).ToList(),
                WinRate = item.GetWinRate(),
                ProfitAndLoss = item.GetProfitAndLoss(),
                Identifier = item.Identifier,
                AgentName = item.User.AgentName,
                Config = item.Config // Contains all configuration properties
            });
        }

        return list;
    }

    /// <summary>
    /// Notifies subscribers about the current list of bots via SignalR.
    /// </summary>
    private async Task NotifyBotSubscriberAsync()
    {
        var botsList = await GetBotList();
        await _hubContext.Clients.All.SendAsync("BotsSubscription", botsList);
    }

    /// <summary>
    /// Manually opens a position for a specified bot with the given parameters.
    /// </summary>
    /// <param name="request">The request containing position parameters.</param>
    /// <returns>A response indicating the result of the operation.</returns>
    [HttpPost]
    [Route("OpenPosition")]
    public async Task<ActionResult<Position>> OpenPositionManually([FromBody] OpenPositionManuallyRequest request)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(request.Identifier))
            {
                return Forbid("You don't have permission to open positions for this bot");
            }

            var activeBots = _botService.GetActiveBots();
            var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier);

            if (bot == null)
            {
                return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot");
            }

            if (bot.GetStatus() != BotStatus.Up.ToString())
            {
                return BadRequest($"Bot with identifier {request.Identifier} is not running");
            }

            var position = await bot.OpenPositionManually(
                request.Direction
            );

            await NotifyBotSubscriberAsync();
            return Ok(position);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error opening position manually");
            return StatusCode(500, $"Error opening position: {ex.Message}");
        }
    }

    /// <summary>
    /// Closes a specific position for a trading bot
    /// </summary>
    /// <param name="request">The request containing the position close parameters</param>
    /// <returns>The closed position or an error</returns>
    [HttpPost]
    [Route("ClosePosition")]
    public async Task<ActionResult<Position>> ClosePosition([FromBody] ClosePositionRequest request)
    {
        try
        {
            // Check if user owns the account
            if (!await UserOwnsBotAccount(request.Identifier))
            {
                return Forbid("You don't have permission to close positions for this bot");
            }

            var activeBots = _botService.GetActiveBots();
            var bot = activeBots.FirstOrDefault(b => b.Identifier == request.Identifier);

            if (bot == null)
            {
                return NotFound($"Bot with identifier {request.Identifier} not found or is not a trading bot");
            }

            if (bot.GetStatus() != BotStatus.Up.ToString())
            {
                return BadRequest($"Bot with identifier {request.Identifier} is not running");
            }

            // Find the position to close
            var position = bot.Positions.FirstOrDefault(p => p.Identifier == request.PositionId);
            if (position == null)
            {
                return NotFound(
                    $"Position with ID {request.PositionId} not found for bot with identifier {request.Identifier}");
            }

            // Find the signal associated with this position
            var signal = bot.Signals.FirstOrDefault(s => s.Identifier == position.SignalIdentifier);
            if (signal == null)
            {
                return NotFound($"Signal not found for position {request.PositionId}");
            }

            // Get current price
            var lastCandle = bot.OptimizedCandles.LastOrDefault();
            if (lastCandle == null)
            {
                return BadRequest("Cannot get current price to close position");
            }

            // Close the position at market price
            await bot.CloseTrade(signal, position, position.Open, lastCandle.Close, true);

            await NotifyBotSubscriberAsync();
            return Ok(position);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error closing position");
            return StatusCode(500, $"Error closing position: {ex.Message}");
        }
    }

    /// <summary>
    /// Updates the configuration of an existing bot.
    /// </summary>
    /// <param name="request">The update request containing the bot identifier and new configuration</param>
    /// <returns>Success message</returns>
    [HttpPut]
    [Route("UpdateConfig")]
    public async Task<ActionResult<string>> UpdateBotConfig([FromBody] UpdateBotConfigRequest request)
    {
        try
        {
            var user = await GetUser();
            if (user == null)
            {
                return Unauthorized("User not found");
            }

            if (string.IsNullOrEmpty(request.Identifier))
            {
                return BadRequest("Bot identifier is required");
            }

            if (request.Config == null)
            {
                return BadRequest("Bot configuration is required");
            }

            // First, check if the user owns the existing bot
            if (!await UserOwnsBotAccount(request.Identifier))
            {
                return Forbid("You don't have permission to update this bot");
            }

            // Get the existing bot to ensure it exists and get current config
            var bots = _botService.GetActiveBots();
            var existingBot = bots.FirstOrDefault(b => b.Identifier == request.Identifier);
            
            if (existingBot == null)
            {
                return NotFound($"Bot with identifier '{request.Identifier}' not found");
            }

            // If the account is being changed, verify the user owns the new account too
            if (existingBot.Config.AccountName != request.Config.AccountName)
            {
                if (!await UserOwnsBotAccount(null, request.Config.AccountName))
                {
                    return Forbid("You don't have permission to use this account");
                }
            }

            // If the bot name is being changed, check for conflicts
            var isNameChanging = !string.IsNullOrEmpty(request.Config.Name) && 
                                 request.Config.Name != request.Identifier;
            
            if (isNameChanging)
            {
                // Check if new name already exists
                var existingBotWithNewName = bots.FirstOrDefault(b => b.Identifier == request.Config.Name);
                if (existingBotWithNewName != null)
                {
                    return BadRequest($"A bot with the name '{request.Config.Name}' already exists");
                }
            }

            // Validate the money management if provided
            if (request.Config.MoneyManagement != null)
            {
                // Check if the money management belongs to the user
                var userMoneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.Config.MoneyManagement.Name);
                if (userMoneyManagement != null && userMoneyManagement.User?.Name != user.Name)
                {
                    return Forbid("You don't have permission to use this money management");
                }
            }
            else if (!string.IsNullOrEmpty(request.MoneyManagementName))
            {
                // If MoneyManagement is null but MoneyManagementName is provided, load it
                var moneyManagement = await _moneyManagementService.GetMoneyMangement(user, request.MoneyManagementName);
                if (moneyManagement == null)
                {
                    return BadRequest($"Money management '{request.MoneyManagementName}' not found");
                }
                
                if (moneyManagement.User?.Name != user.Name)
                {
                    return Forbid("You don't have permission to use this money management");
                }
                
                request.Config.MoneyManagement = moneyManagement;
            }

            // Validate CloseEarlyWhenProfitable requires MaxPositionTimeHours
            if (request.Config.CloseEarlyWhenProfitable && !request.Config.MaxPositionTimeHours.HasValue)
            {
                return BadRequest("CloseEarlyWhenProfitable requires MaxPositionTimeHours to be set");
            }

            // Update the bot configuration using the enhanced method
            var success = await _botService.UpdateBotConfiguration(request.Identifier, request.Config);
            
            if (success)
            {
                var finalBotName = isNameChanging ? request.Config.Name : request.Identifier;
                
                await _hubContext.Clients.All.SendAsync("SendNotification",
                    $"Bot {finalBotName} configuration updated successfully by {user.Name}." +
                    (isNameChanging ? $" (renamed from {request.Identifier})" : ""), "Info");
                
                await NotifyBotSubscriberAsync();
                
                return Ok(isNameChanging 
                    ? $"Bot configuration updated successfully and renamed to '{request.Config.Name}'"
                    : "Bot configuration updated successfully");
            }
            else
            {
                return BadRequest("Failed to update bot configuration. " +
                    (isNameChanging ? "The new name might already be in use." : ""));
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error updating bot configuration for identifier {Identifier}", request.Identifier);
            return StatusCode(500, $"Error updating bot configuration: {ex.Message}");
        }
    }
}

/// <summary>
/// Request model for opening a position manually
/// </summary>
public class OpenPositionManuallyRequest
{
    /// <summary>
    /// The identifier of the bot
    /// </summary>
    public string Identifier { get; set; }

    /// <summary>
    /// The direction of the position
    /// </summary>
    public TradeDirection Direction { get; set; }
}

/// <summary>
/// Request model for closing a position
/// </summary>
public class ClosePositionRequest
{
    /// <summary>
    /// The identifier of the bot
    /// </summary>
    public string Identifier { get; set; }

    /// <summary>
    /// The ID of the position to close
    /// </summary>
    public string PositionId { get; set; }
}

/// <summary>
/// Request model for starting a bot
/// </summary>
public class StartBotRequest
{
    /// <summary>
    /// The trading bot configuration
    /// </summary>
    public TradingBotConfig Config { get; set; }

    /// <summary>
    /// Optional money management name (if not included in Config.MoneyManagement)
    /// </summary>
    public string? MoneyManagementName { get; set; }
}

/// <summary>
/// Request model for updating bot configuration
/// </summary>
public class UpdateBotConfigRequest
{
    /// <summary>
    /// The unique identifier of the bot to update
    /// </summary>
    [Required]
    public string Identifier { get; set; }

    /// <summary>
    /// The new trading bot configuration
    /// </summary>
    [Required]
    public TradingBotConfig Config { get; set; }

    /// <summary>
    /// Optional: Money management name to load if Config.MoneyManagement is null
    /// </summary>
    public string? MoneyManagementName { get; set; }
}