using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Services;
using Managing.Application.Shared;
using Managing.Application.Trading.Commands;
using Managing.Domain.MoneyManagements;
using Managing.Domain.Trades;
using Managing.Infrastructure.Evm.Models.Privy;
using MediatR;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using static Managing.Common.Enums;

namespace Managing.Api.Controllers;

/// <summary>
/// Controller for trading operations such as opening and closing positions, and retrieving trade information.
/// Requires authorization for access.
/// </summary>
[ApiController]
[Authorize]
[Route("[controller]")]
public class TradingController : BaseController
{
    private readonly ICommandHandler<OpenPositionRequest, Position> _openTradeCommandHandler;
    private readonly ICommandHandler<ClosePositionCommand, Position> _closeTradeCommandHandler;
    private readonly ITradingService _tradingService;
    private readonly IMoneyManagementService _moneyManagementService;
    private readonly IMediator _mediator;
    private readonly ILogger<TradingController> _logger;
    private readonly IAdminConfigurationService _adminService;
    private readonly IAccountService _accountService;

    /// <summary>
    /// Initializes a new instance of the <see cref="TradingController"/> class.
    /// </summary>
    /// <param name="logger">Logger for logging information.</param>
    /// <param name="openTradeCommandHandler">Command handler for opening trades.</param>
    /// <param name="closeTradeCommandHandler">Command handler for closing trades.</param>
    /// <param name="tradingService">Service for trading operations.</param>
    /// <param name="mediator">Mediator for handling commands and requests.</param>
    /// <param name="adminService">Service for checking admin privileges.</param>
    /// <param name="accountService">Service for account operations.</param>
    public TradingController(
        ILogger<TradingController> logger,
        ICommandHandler<OpenPositionRequest, Position> openTradeCommandHandler,
        ICommandHandler<ClosePositionCommand, Position> closeTradeCommandHandler,
        ITradingService tradingService,
        IMediator mediator, IMoneyManagementService moneyManagementService,
        IUserService userService, IAdminConfigurationService adminService,
        IAccountService accountService) : base(userService)
    {
        _logger = logger;
        _openTradeCommandHandler = openTradeCommandHandler;
        _closeTradeCommandHandler = closeTradeCommandHandler;
        _tradingService = tradingService;
        _mediator = mediator;
        _moneyManagementService = moneyManagementService;
        _adminService = adminService;
        _accountService = accountService;
    }


    /// <summary>
    /// Retrieves a specific trade by account name, ticker, and exchange order ID.
    /// </summary>
    /// <param name="accountName">The name of the account.</param>
    /// <param name="ticker">The ticker symbol of the trade.</param>
    /// <param name="exchangeOrderId">The exchange order ID of the trade.</param>
    /// <returns>The requested trade.</returns>
    [HttpGet("GetTrade")]
    public async Task<ActionResult<Trade>> GetTrade(string accountName, Ticker ticker, string exchangeOrderId)
    {
        var result = await _mediator.Send(new GetTradeCommand(accountName, exchangeOrderId, ticker));
        return Ok(result);
    }

    /// <summary>
    /// Retrieves a list of trades for a given account and ticker.
    /// </summary>
    /// <param name="accountName">The name of the account.</param>
    /// <param name="ticker">The ticker symbol of the trades.</param>
    /// <returns>A list of trades.</returns>
    [HttpGet("GetTrades")]
    public async Task<ActionResult<Trade>> GetTrades(string accountName, Ticker ticker)
    {
        var result = await _mediator.Send(new GetTradesCommand(ticker, accountName));
        return Ok(result);
    }

    /// <summary>
    /// Closes a position identified by its unique identifier.
    /// </summary>
    /// <param name="identifier">The unique identifier of the position to close.</param>
    /// <returns>The closed position.</returns>
    [HttpPost("ClosePosition")]
    public async Task<ActionResult<Position>> ClosePosition(Guid identifier)
    {
        var position = await _tradingService.GetPositionByIdentifierAsync(identifier);
        var result = await _closeTradeCommandHandler.Handle(new ClosePositionCommand(position));
        return Ok(result);
    }

    /// <summary>
    /// Opens a new position based on the provided parameters.
    /// </summary>
    /// <param name="accountName">The name of the account to open the position for.</param>
    /// <param name="moneyManagementName">The name of the money management strategy to use.</param>
    /// <param name="direction">The direction of the trade (Buy or Sell).</param>
    /// <param name="ticker">The ticker symbol for the trade.</param>
    /// <param name="riskLevel">The risk level for the trade.</param>
    /// <param name="isForPaperTrading">Indicates whether the trade is for paper trading.</param>
    /// <param name="moneyManagement">The money management strategy details (optional).</param>
    /// <param name="openPrice">The opening price for the trade (optional).</param>
    /// <returns>The opened position.</returns>
    [HttpPost("OpenPosition")]
    public async Task<ActionResult<Position>> Trade(
        string accountName,
        string moneyManagementName,
        TradeDirection direction,
        Ticker ticker,
        RiskLevel riskLevel,
        bool isForPaperTrading,
        MoneyManagement? moneyManagement = null,
        decimal? openPrice = null)
    {
        if (string.IsNullOrEmpty(accountName))
        {
            throw new ArgumentException($"'{nameof(accountName)}' cannot be null or empty.", nameof(accountName));
        }

        if (string.IsNullOrEmpty(moneyManagementName) && moneyManagement == null)
        {
            throw new ArgumentException($"'{nameof(moneyManagementName)}' cannot be null or empty.",
                nameof(moneyManagementName));
        }

        var user = await GetUser();
        if (moneyManagement != null)
        {
            moneyManagement = await _moneyManagementService.GetMoneyMangement(user, moneyManagementName);
        }

        var command = new OpenPositionRequest(
            accountName,
            moneyManagement,
            direction,
            ticker,
            PositionInitiator.User,
            DateTime.UtcNow,
            user,
            100m, // Default trading balance for user-initiated trades
            isForPaperTrading: isForPaperTrading,
            price: openPrice);
        var result = await _openTradeCommandHandler.Handle(command);
        return Ok(result);
    }

    /// <summary>
    /// Initializes a Privy wallet address for the user.
    /// Only admins can initialize any address, regular users can only initialize their own addresses.
    /// </summary>
    /// <param name="publicAddress">The public address of the Privy wallet to initialize.</param>
    /// <returns>The initialization response containing success status and transaction hashes.</returns>
    [HttpPost("InitPrivyWallet")]
    public async Task<ActionResult<PrivyInitAddressResponse>> InitPrivyWallet([FromBody] string publicAddress)
    {
        if (string.IsNullOrEmpty(publicAddress))
        {
            return BadRequest("Public address cannot be null or empty.");
        }

        try
        {
            var user = await GetUser();
            if (user == null)
            {
                return Unauthorized("User not found");
            }

            // Check if user has permission to initialize this address
            if (!await CanUserInitializeAddress(user.Name, publicAddress))
            {
                return Forbid("You don't have permission to initialize this wallet address. You can only initialize your own wallet addresses.");
            }

            var result = await _tradingService.InitPrivyWallet(publicAddress);
            return Ok(result);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Error initializing Privy wallet address: {Address}", publicAddress);
            return StatusCode(500, new PrivyInitAddressResponse
            {
                Success = false,
                Error = "An error occurred while initializing the Privy wallet address."
            });
        }
    }

    /// <summary>
    /// Checks if the user can initialize the given public address.
    /// Admins can initialize any address, regular users can only initialize their own addresses.
    /// </summary>
    /// <param name="userName">The username to check</param>
    /// <param name="publicAddress">The public address to initialize</param>
    /// <returns>True if the user can initialize the address, false otherwise</returns>
    private async Task<bool> CanUserInitializeAddress(string userName, string publicAddress)
    {
        // Admin users can initialize any address
        if (_adminService.IsUserAdmin(userName))
        {
            _logger.LogInformation("Admin user {UserName} initializing address {Address}", userName, publicAddress);
            return true;
        }

        try
        {
            // Regular users can only initialize their own addresses
            // Check if the address belongs to one of the user's accounts
            var account = await _accountService.GetAccountByKey(publicAddress, true, false);
            
            if (account?.User?.Name == userName)
            {
                _logger.LogInformation("User {UserName} initializing their own address {Address}", userName, publicAddress);
                return true;
            }
            
            _logger.LogWarning("User {UserName} attempted to initialize address {Address} that doesn't belong to them", userName, publicAddress);
            return false;
        }
        catch (Exception ex)
        {
            _logger.LogWarning(ex, "Unable to verify ownership of address {Address} for user {UserName}", publicAddress, userName);
            return false;
        }
    }
}