using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using LittleShop.Data;
using LittleShop.DTOs;
using LittleShop.Enums;
using LittleShop.Models;

namespace LittleShop.Services;

public class BotService : IBotService
{
    private readonly LittleShopContext _context;
    private readonly ILogger<BotService> _logger;

    public BotService(LittleShopContext context, ILogger<BotService> logger)
    {
        _context = context;
        _logger = logger;
    }

    public async Task<BotRegistrationResponseDto> RegisterBotAsync(BotRegistrationDto dto)
    {
        _logger.LogInformation("Registering new bot: {BotName}", dto.Name);

        var botKey = await GenerateBotKeyAsync();
        
        var bot = new Bot
        {
            Id = Guid.NewGuid(),
            Name = dto.Name,
            Description = dto.Description,
            Type = dto.Type,
            BotKey = botKey,
            Status = BotStatus.Active,
            Settings = JsonSerializer.Serialize(dto.InitialSettings),
            Version = dto.Version,
            PersonalityName = string.IsNullOrEmpty(dto.PersonalityName) ? AssignDefaultPersonality(dto.Name) : dto.PersonalityName,
            CreatedAt = DateTime.UtcNow,
            IsActive = true
        };

        _context.Bots.Add(bot);
        await _context.SaveChangesAsync();

        _logger.LogInformation("Bot registered successfully: {BotId}", bot.Id);

        return new BotRegistrationResponseDto
        {
            BotId = bot.Id,
            BotKey = botKey,
            Name = bot.Name,
            Settings = dto.InitialSettings
        };
    }

    public async Task<BotDto?> AuthenticateBotAsync(string botKey)
    {
        var bot = await _context.Bots
            .Include(b => b.Sessions)
            .Include(b => b.Metrics)
            .FirstOrDefaultAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active);

        if (bot == null)
        {
            _logger.LogWarning("Authentication failed for bot key: {BotKey}", botKey.Substring(0, 8) + "...");
            return null;
        }

        // Update last seen
        bot.LastSeenAt = DateTime.UtcNow;
        await _context.SaveChangesAsync();

        return MapToDto(bot);
    }

    public async Task<BotDto?> GetBotByIdAsync(Guid id)
    {
        var bot = await _context.Bots
            .Include(b => b.Sessions)
            .Include(b => b.Metrics)
            .FirstOrDefaultAsync(b => b.Id == id);

        return bot != null ? MapToDto(bot) : null;
    }

    public async Task<BotDto?> GetBotByKeyAsync(string botKey)
    {
        var bot = await _context.Bots
            .Include(b => b.Sessions)
            .Include(b => b.Metrics)
            .FirstOrDefaultAsync(b => b.BotKey == botKey);

        return bot != null ? MapToDto(bot) : null;
    }

    public async Task<IEnumerable<BotDto>> GetAllBotsAsync()
    {
        try
        {
            var bots = await _context.Bots
                .Include(b => b.Sessions)
                .Include(b => b.Metrics)
                .OrderByDescending(b => b.CreatedAt)
                .ToListAsync();

            return bots.Select(MapToDto);
        }
        catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table"))
        {
            // Tables don't exist yet - return empty list
            return new List<BotDto>();
        }
    }

    public async Task<IEnumerable<BotDto>> GetActiveBots()
    {
        try
        {
            var bots = await _context.Bots
                .Include(b => b.Sessions)
                .Include(b => b.Metrics)
                .Where(b => b.Status == BotStatus.Active && b.IsActive)
                .OrderByDescending(b => b.LastSeenAt)
                .ToListAsync();

            return bots.Select(MapToDto);
        }
        catch (Microsoft.Data.Sqlite.SqliteException ex) when (ex.Message.Contains("no such table"))
        {
            // Tables don't exist yet - return empty list
            return new List<BotDto>();
        }
    }

    public async Task<bool> UpdateBotSettingsAsync(Guid botId, UpdateBotSettingsDto dto)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return false;

        bot.Settings = JsonSerializer.Serialize(dto.Settings);
        bot.LastConfigSyncAt = DateTime.UtcNow;
        
        await _context.SaveChangesAsync();
        
        _logger.LogInformation("Updated settings for bot {BotId}", botId);
        return true;
    }

    public async Task<bool> UpdateBotStatusAsync(Guid botId, BotStatus status)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return false;

        var oldStatus = bot.Status;
        bot.Status = status;
        bot.IsActive = status == BotStatus.Active;
        
        await _context.SaveChangesAsync();
        
        _logger.LogInformation("Bot {BotId} status changed from {OldStatus} to {NewStatus}", 
            botId, oldStatus, status);
        return true;
    }

    public async Task<bool> RecordHeartbeatAsync(Guid botId, BotHeartbeatDto dto)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return false;

        bot.LastSeenAt = DateTime.UtcNow;
        bot.Version = dto.Version;
        bot.IpAddress = dto.IpAddress;
        
        // Record uptime metric
        var uptimeMetric = new BotMetric
        {
            Id = Guid.NewGuid(),
            BotId = botId,
            MetricType = MetricType.Uptime,
            Value = 1,
            Metadata = JsonSerializer.Serialize(dto.Status),
            RecordedAt = DateTime.UtcNow,
            Category = "System",
            Description = "Heartbeat"
        };
        
        _context.BotMetrics.Add(uptimeMetric);
        await _context.SaveChangesAsync();
        
        return true;
    }

    public async Task<bool> DeleteBotAsync(Guid botId)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return false;

        bot.Status = BotStatus.Deleted;
        bot.IsActive = false;
        
        await _context.SaveChangesAsync();
        
        _logger.LogInformation("Bot {BotId} marked as deleted", botId);
        return true;
    }

    public async Task<Dictionary<string, object>> GetBotSettingsAsync(Guid botId)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return new Dictionary<string, object>();

        try
        {
            return JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings) 
                ?? new Dictionary<string, object>();
        }
        catch
        {
            return new Dictionary<string, object>();
        }
    }

    public async Task<bool> ValidateBotKeyAsync(string botKey)
    {
        return await _context.Bots.AnyAsync(b => b.BotKey == botKey && b.Status == BotStatus.Active);
    }

    public Task<string> GenerateBotKeyAsync()
    {
        const string prefix = "bot_";
        const int keyLength = 32;
        
        using var rng = RandomNumberGenerator.Create();
        var bytes = new byte[keyLength];
        rng.GetBytes(bytes);
        
        var key = prefix + Convert.ToBase64String(bytes)
            .Replace("+", "")
            .Replace("/", "")
            .Replace("=", "")
            .Substring(0, keyLength);
        
        return Task.FromResult(key);
    }

    public async Task<bool> UpdatePlatformInfoAsync(Guid botId, UpdatePlatformInfoDto dto)
    {
        var bot = await _context.Bots.FindAsync(botId);
        if (bot == null)
            return false;

        bot.PlatformUsername = dto.PlatformUsername;
        bot.PlatformDisplayName = dto.PlatformDisplayName;
        bot.PlatformId = dto.PlatformId;
        
        await _context.SaveChangesAsync();
        
        _logger.LogInformation("Updated platform info for bot {BotId}: @{Username} ({DisplayName})", 
            botId, dto.PlatformUsername, dto.PlatformDisplayName);
        return true;
    }

    private BotDto MapToDto(Bot bot)
    {
        var settings = new Dictionary<string, object>();
        try
        {
            settings = JsonSerializer.Deserialize<Dictionary<string, object>>(bot.Settings) 
                ?? new Dictionary<string, object>();
        }
        catch { }

        var activeSessions = bot.Sessions.Count(s => !s.EndedAt.HasValue);
        var totalRevenue = bot.Sessions.Sum(s => s.TotalSpent);
        var totalOrders = bot.Sessions.Sum(s => s.OrderCount);

        return new BotDto
        {
            Id = bot.Id,
            Name = bot.Name,
            Description = bot.Description,
            Type = bot.Type,
            Status = bot.Status,
            CreatedAt = bot.CreatedAt,
            LastSeenAt = bot.LastSeenAt,
            LastConfigSyncAt = bot.LastConfigSyncAt,
            IsActive = bot.IsActive,
            Version = bot.Version,
            IpAddress = bot.IpAddress,
            PlatformUsername = bot.PlatformUsername,
            PlatformDisplayName = bot.PlatformDisplayName,
            PlatformId = bot.PlatformId,
            PersonalityName = bot.PersonalityName,
            Settings = settings,
            TotalSessions = bot.Sessions.Count,
            ActiveSessions = activeSessions,
            TotalRevenue = totalRevenue,
            TotalOrders = totalOrders
        };
    }

    private string AssignDefaultPersonality(string botName)
    {
        var personalities = new[] { "Alan", "Dave", "Sarah", "Mike", "Emma", "Tom" };
        
        // Smart assignment based on name
        var lowerName = botName.ToLower();
        if (lowerName.Contains("alan")) return "Alan";
        if (lowerName.Contains("dave")) return "Dave";
        if (lowerName.Contains("sarah")) return "Sarah";
        if (lowerName.Contains("mike")) return "Mike";
        if (lowerName.Contains("emma")) return "Emma";
        if (lowerName.Contains("tom")) return "Tom";
        
        // Random assignment if no match
        var random = new Random();
        return personalities[random.Next(personalities.Length)];
    }
}