using System.ComponentModel.DataAnnotations;
using DysonNetwork.Develop.Project;
using DysonNetwork.Shared.Models;
using DysonNetwork.Shared.Proto;
using DysonNetwork.Shared.Registry;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using NodaTime;
using NodaTime.Serialization.Protobuf;

namespace DysonNetwork.Develop.Identity;

[ApiController]
[Route("/api/developers/{pubName}/projects/{projectId:guid}/bots")]
[Authorize]
public class BotAccountController(
    BotAccountService botService,
    DeveloperService ds,
    DevProjectService projectService,
    ILogger<BotAccountController> logger,
    RemoteAccountService remoteAccounts,
    BotAccountReceiverService.BotAccountReceiverServiceClient accountsReceiver
)
    : ControllerBase
{
    public class CommonBotRequest
    {
        [MaxLength(256)] public string? FirstName { get; set; }
        [MaxLength(256)] public string? MiddleName { get; set; }
        [MaxLength(256)] public string? LastName { get; set; }
        [MaxLength(1024)] public string? Gender { get; set; }
        [MaxLength(1024)] public string? Pronouns { get; set; }
        [MaxLength(1024)] public string? TimeZone { get; set; }
        [MaxLength(1024)] public string? Location { get; set; }
        [MaxLength(4096)] public string? Bio { get; set; }
        public Instant? Birthday { get; set; }

        [MaxLength(32)] public string? PictureId { get; set; }
        [MaxLength(32)] public string? BackgroundId { get; set; }
    }

    public class BotCreateRequest : CommonBotRequest
    {
        [Required]
        [MinLength(2)]
        [MaxLength(256)]
        [RegularExpression(@"^[A-Za-z0-9_-]+$",
            ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.")
        ]
        public string Name { get; set; } = string.Empty;

        [Required][MaxLength(256)] public string Nick { get; set; } = string.Empty;

        [Required][MaxLength(1024)] public string Slug { get; set; } = string.Empty;

        [MaxLength(128)] public string Language { get; set; } = "en-us";
    }

    public class UpdateBotRequest : CommonBotRequest
    {
        [MinLength(2)]
        [MaxLength(256)]
        [RegularExpression(@"^[A-Za-z0-9_-]+$",
            ErrorMessage = "Name can only contain letters, numbers, underscores, and hyphens.")
        ]
        public string? Name { get; set; } = string.Empty;

        [MaxLength(256)] public string? Nick { get; set; } = string.Empty;

        [Required][MaxLength(1024)] public string? Slug { get; set; } = string.Empty;

        [MaxLength(128)] public string? Language { get; set; }

        public bool? IsActive { get; set; }
    }

    [HttpGet]
    public async Task<IActionResult> ListBots(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var developer = await ds.GetDeveloperByName(pubName);
        if (developer is null)
            return NotFound("Developer not found");

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
                Shared.Proto.PublisherMemberRole.Viewer))
            return StatusCode(403, "You must be an viewer of the developer to list bots");

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project is null)
            return NotFound("Project not found or you don't have access");

        var bots = await botService.GetBotsByProjectAsync(projectId);
        return Ok(await botService.LoadBotsAccountAsync(bots));
    }

    [HttpGet("{botId:guid}")]
    public async Task<IActionResult> GetBot(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var developer = await ds.GetDeveloperByName(pubName);
        if (developer is null)
            return NotFound("Developer not found");

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
                Shared.Proto.PublisherMemberRole.Viewer))
            return StatusCode(403, "You must be an viewer of the developer to view bot details");

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project is null)
            return NotFound("Project not found or you don't have access");

        var bot = await botService.GetBotByIdAsync(botId);
        if (bot is null || bot.ProjectId != projectId)
            return NotFound("Bot not found");

        return Ok(await botService.LoadBotAccountAsync(bot));
    }

    [HttpPost]
    public async Task<IActionResult> CreateBot(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromBody] BotCreateRequest createRequest
    )
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var developer = await ds.GetDeveloperByName(pubName);
        if (developer is null)
            return NotFound("Developer not found");

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
                Shared.Proto.PublisherMemberRole.Editor))
            return StatusCode(403, "You must be an editor of the developer to create a bot");

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project is null)
            return NotFound("Project not found or you don't have access");

        var now = SystemClock.Instance.GetCurrentInstant();
        var accountId = Guid.NewGuid();
        var account = new Account()
        {
            Id = accountId.ToString(),
            Name = createRequest.Name,
            Nick = createRequest.Nick,
            Language = createRequest.Language,
            Profile = new AccountProfile()
            {
                Id = Guid.NewGuid().ToString(),
                Bio = createRequest.Bio,
                Gender = createRequest.Gender,
                FirstName = createRequest.FirstName,
                MiddleName = createRequest.MiddleName,
                LastName = createRequest.LastName,
                TimeZone = createRequest.TimeZone,
                Pronouns = createRequest.Pronouns,
                Location = createRequest.Location,
                Birthday = createRequest.Birthday?.ToTimestamp(),
                AccountId = accountId.ToString(),
                CreatedAt = now.ToTimestamp(),
                UpdatedAt = now.ToTimestamp()
            },
            CreatedAt = now.ToTimestamp(),
            UpdatedAt = now.ToTimestamp()
        };

        try
        {
            var bot = await botService.CreateBotAsync(
                project,
                createRequest.Slug,
                account,
                createRequest.PictureId,
                createRequest.BackgroundId
            );
            return Ok(bot);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error creating bot account");
            return StatusCode(500, "An error occurred while creating the bot account");
        }
    }

    [HttpPatch("{botId:guid}")]
    public async Task<IActionResult> UpdateBot(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId,
        [FromBody] UpdateBotRequest request
    )
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var developer = await ds.GetDeveloperByName(pubName);
        if (developer is null)
            return NotFound("Developer not found");

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
                Shared.Proto.PublisherMemberRole.Editor))
            return StatusCode(403, "You must be an editor of the developer to update a bot");

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project is null)
            return NotFound("Project not found or you don't have access");

        var bot = await botService.GetBotByIdAsync(botId);
        if (bot is null || bot.ProjectId != projectId)
            return NotFound("Bot not found");

        var botAccount = await remoteAccounts.GetBotAccount(bot.Id);

        if (request.Name is not null) botAccount.Name = request.Name;
        if (request.Nick is not null) botAccount.Nick = request.Nick;
        if (request.Language is not null) botAccount.Language = request.Language;
        if (request.Bio is not null) botAccount.Profile.Bio = request.Bio;
        if (request.Gender is not null) botAccount.Profile.Gender = request.Gender;
        if (request.FirstName is not null) botAccount.Profile.FirstName = request.FirstName;
        if (request.MiddleName is not null) botAccount.Profile.MiddleName = request.MiddleName;
        if (request.LastName is not null) botAccount.Profile.LastName = request.LastName;
        if (request.TimeZone is not null) botAccount.Profile.TimeZone = request.TimeZone;
        if (request.Pronouns is not null) botAccount.Profile.Pronouns = request.Pronouns;
        if (request.Location is not null) botAccount.Profile.Location = request.Location;
        if (request.Birthday is not null) botAccount.Profile.Birthday = request.Birthday?.ToTimestamp();

        if (request.Slug is not null) bot.Slug = request.Slug;
        if (request.IsActive is not null) bot.IsActive = request.IsActive.Value;

        try
        {
            var updatedBot = await botService.UpdateBotAsync(
                bot,
                botAccount,
                request.PictureId,
                request.BackgroundId
            );

            return Ok(updatedBot);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error updating bot account {BotId}", botId);
            return StatusCode(500, "An error occurred while updating the bot account");
        }
    }

    [HttpDelete("{botId:guid}")]
    public async Task<IActionResult> DeleteBot(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var developer = await ds.GetDeveloperByName(pubName);
        if (developer is null)
            return NotFound("Developer not found");

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id),
                Shared.Proto.PublisherMemberRole.Editor))
            return StatusCode(403, "You must be an editor of the developer to delete a bot");

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project is null)
            return NotFound("Project not found or you don't have access");

        var bot = await botService.GetBotByIdAsync(botId);
        if (bot is null || bot.ProjectId != projectId)
            return NotFound("Bot not found");

        try
        {
            await botService.DeleteBotAsync(bot);
            return NoContent();
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Error deleting bot {BotId}", botId);
            return StatusCode(500, "An error occurred while deleting the bot account");
        }
    }

    [HttpGet("{botId:guid}/keys")]
    public async Task<ActionResult<List<SnApiKey>>> ListBotKeys(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId
    )
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Viewer);
        if (developer == null) return NotFound("Developer not found");
        if (project == null) return NotFound("Project not found or you don't have access");
        if (bot == null) return NotFound("Bot not found");

        var keys = await accountsReceiver.ListApiKeyAsync(new ListApiKeyRequest
        {
            AutomatedId = bot.Id.ToString()
        });
        var data = keys.Data.Select(SnApiKey.FromProtoValue).ToList();

        return Ok(data);
    }

    [HttpGet("{botId:guid}/keys/{keyId:guid}")]
    public async Task<ActionResult<SnApiKey>> GetBotKey(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId,
        [FromRoute] Guid keyId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Viewer);
        if (developer == null) return NotFound("Developer not found");
        if (project == null) return NotFound("Project not found or you don't have access");
        if (bot == null) return NotFound("Bot not found");

        try
        {
            var key = await accountsReceiver.GetApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() });
            if (key == null) return NotFound("API key not found");
            return Ok(SnApiKey.FromProtoValue(key));
        }
        catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
        {
            return NotFound("API key not found");
        }
    }

    public class CreateApiKeyRequest
    {
        [Required, MaxLength(1024)]
        public string Label { get; set; } = null!;
    }

    [HttpPost("{botId:guid}/keys")]
    public async Task<ActionResult<SnApiKey>> CreateBotKey(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId,
        [FromBody] CreateApiKeyRequest request)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
        if (developer == null) return NotFound("Developer not found");
        if (project == null) return NotFound("Project not found or you don't have access");
        if (bot == null) return NotFound("Bot not found");

        try
        {
            var newKey = new ApiKey
            {
                AccountId = bot.Id.ToString(),
                Label = request.Label
            };

            var createdKey = await accountsReceiver.CreateApiKeyAsync(newKey);
            return Ok(SnApiKey.FromProtoValue(createdKey));
        }
        catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.InvalidArgument)
        {
            return BadRequest(ex.Status.Detail);
        }
    }

    [HttpPost("{botId:guid}/keys/{keyId:guid}/rotate")]
    public async Task<ActionResult<SnApiKey>> RotateBotKey(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId,
        [FromRoute] Guid keyId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
        if (developer == null) return NotFound("Developer not found");
        if (project == null) return NotFound("Project not found or you don't have access");
        if (bot == null) return NotFound("Bot not found");

        try
        {
            var rotatedKey = await accountsReceiver.RotateApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() });
            return Ok(SnApiKey.FromProtoValue(rotatedKey));
        }
        catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
        {
            return NotFound("API key not found");
        }
    }

    [HttpDelete("{botId:guid}/keys/{keyId:guid}")]
    public async Task<IActionResult> DeleteBotKey(
        [FromRoute] string pubName,
        [FromRoute] Guid projectId,
        [FromRoute] Guid botId,
        [FromRoute] Guid keyId)
    {
        if (HttpContext.Items["CurrentUser"] is not Account currentUser)
            return Unauthorized();

        var (developer, project, bot) = await ValidateBotAccess(pubName, projectId, botId, currentUser, Shared.Proto.PublisherMemberRole.Editor);
        if (developer == null) return NotFound("Developer not found");
        if (project == null) return NotFound("Project not found or you don't have access");
        if (bot == null) return NotFound("Bot not found");

        try
        {
            await accountsReceiver.DeleteApiKeyAsync(new GetApiKeyRequest { Id = keyId.ToString() });
            return NoContent();
        }
        catch (RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound)
        {
            return NotFound("API key not found");
        }
    }

    private async Task<(SnDeveloper?, SnDevProject?, SnBotAccount?)> ValidateBotAccess(
        string pubName,
        Guid projectId,
        Guid botId,
        Account currentUser,
        Shared.Proto.PublisherMemberRole requiredRole)
    {
        var developer = await ds.GetDeveloperByName(pubName);
        if (developer == null) return (null, null, null);

        if (!await ds.IsMemberWithRole(developer.PublisherId, Guid.Parse(currentUser.Id), requiredRole))
            return (null, null, null);

        var project = await projectService.GetProjectAsync(projectId, developer.Id);
        if (project == null) return (developer, null, null);

        var bot = await botService.GetBotByIdAsync(botId);
        if (bot == null || bot.ProjectId != projectId) return (developer, project, null);

        return (developer, project, bot);
    }
}
