// Copyright (c) Microsoft Corporation. All rights reserved.
// GithubWebHookProcessor.cs

using System.Globalization;
using Google.Protobuf;
using Microsoft.AutoGen.Contracts;
using Microsoft.AutoGen.Core;
using Octokit.Webhooks;
using Octokit.Webhooks.Events;
using Octokit.Webhooks.Events.IssueComment;
using Octokit.Webhooks.Events.Issues;
using Octokit.Webhooks.Models;

namespace DevTeam.Backend.Services;

public sealed class GithubWebHookProcessor(ILogger<GithubWebHookProcessor> logger, Client client) : WebhookEventProcessor
{
    private readonly ILogger<GithubWebHookProcessor> _logger = logger;
    private readonly Client _client = client;

    protected override async Task ProcessIssuesWebhookAsync(WebhookHeaders headers, IssuesEvent issuesEvent, IssuesAction action)
    {
        try
        {
            ArgumentNullException.ThrowIfNull(headers, nameof(headers));
            ArgumentNullException.ThrowIfNull(issuesEvent, nameof(issuesEvent));
            ArgumentNullException.ThrowIfNull(action, nameof(action));

            _logger.LogInformation("Processing issue event");
            var org = issuesEvent.Repository?.Owner.Login ?? throw new InvalidOperationException("Repository owner login is null");
            var repo = issuesEvent.Repository?.Name ?? throw new InvalidOperationException("Repository name is null");
            var issueNumber = issuesEvent.Issue?.Number ?? throw new InvalidOperationException("Issue number is null");
            var input = issuesEvent.Issue?.Body ?? string.Empty;
            // Assumes the label follows the following convention: Skill.Function example: PM.Readme
            // Also, we've introduced the Parent label, that ties the sub-issue with the parent issue
            var labels = issuesEvent.Issue?.Labels
                                    .Select(l => l.Name.Split('.'))
                                    .Where(parts => parts.Length == 2)
                                    .ToDictionary(parts => parts[0], parts => parts[1]);
            if (labels == null || labels.Count == 0)
            {
                _logger.LogWarning("No labels found in issue. Skipping processing.");
                return;
            }

            long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value) : null;
            var skillName = labels.Keys.Where(k => k != "Parent").FirstOrDefault();

            if (skillName == null)
            {
                _logger.LogWarning("No skill name found in issue. Skipping processing.");
                return;
            }

            var suffix = $"{org}-{repo}";
            if (issuesEvent.Action == IssuesAction.Opened)
            {
                _logger.LogInformation("Processing HandleNewAsk");
                await HandleNewAsk(issueNumber, skillName, labels[skillName], suffix, input, org, repo);
            }
            else if (issuesEvent.Action == IssuesAction.Closed && issuesEvent.Issue?.User.Type.Value == UserType.Bot)
            {
                _logger.LogInformation("Processing HandleClosingIssue");
                await HandleClosingIssue(issueNumber, skillName, labels[skillName], suffix);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Processing issue event");
            throw;
        }
    }

    protected override async Task ProcessIssueCommentWebhookAsync(
       WebhookHeaders headers,
       IssueCommentEvent issueCommentEvent,
       IssueCommentAction action)
    {
        ArgumentNullException.ThrowIfNull(headers);
        ArgumentNullException.ThrowIfNull(issueCommentEvent);
        ArgumentNullException.ThrowIfNull(action);

        try
        {
            _logger.LogInformation("Processing issue comment event");
            var org = issueCommentEvent.Repository!.Owner.Login;
            var repo = issueCommentEvent.Repository.Name;
            var issueNumber = issueCommentEvent.Issue.Number;
            var input = issueCommentEvent.Comment.Body;
            // Assumes the label follows the following convention: Skill.Function example: PM.Readme
            var labels = issueCommentEvent.Issue.Labels
                                    .Select(l => l.Name.Split('.'))
                                    .Where(parts => parts.Length == 2)
                                    .ToDictionary(parts => parts[0], parts => parts[1]);
            var skillName = labels.Keys.First(k => k != "Parent");
            long? parentNumber = labels.TryGetValue("Parent", out var value) ? long.Parse(value, CultureInfo.InvariantCulture) : null;
            var suffix = $"{org}-{repo}";

            // we only respond to non-bot comments
            if (issueCommentEvent.Sender!.Type.Value != UserType.Bot)
            {
                await HandleNewAsk(issueNumber, skillName, labels[skillName], suffix, input, org, repo);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Processing issue comment event");
            throw;
        }

    }

    private async Task HandleClosingIssue(long issueNumber, string skillName, string functionName, string suffix)
    {
        var subject = suffix + issueNumber.ToString();

        IMessage evt = (skillName, functionName) switch
        {
            ("PM", "Readme") => new ReadmeChainClosed { },
            ("DevLead", "Plan") => new DevPlanChainClosed { },
            ("Developer", "Implement") => new CodeChainClosed { },
            _ => new CloudEvent() // TODO: default event
        };

        await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
    }

    private async Task HandleNewAsk(long issueNumber, string skillName, string functionName, string suffix, string input, string org, string repo)
    {
        try
        {
            _logger.LogInformation("Handling new ask");
            var subject = suffix + issueNumber.ToString();

            IMessage evt = (skillName, functionName) switch
            {
                ("Do", "It") => new NewAsk { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
                ("PM", "Readme") => new ReadmeRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
                ("DevLead", "Plan") => new DevPlanRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
                ("Developer", "Implement") => new CodeGenerationRequested { Ask = input, IssueNumber = issueNumber, Org = org, Repo = repo },
                _ => new CloudEvent()
            };
            await _client.PublishMessageAsync(evt, Consts.TopicName, subject);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Handling new ask");
            throw;
        }
    }
}
