package jira

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"log/slog"

	"github.com/spf13/viper"

	"github.com/tidbcloud/jiraport/pkg/api/jira"
	"github.com/tidbcloud/jiraport/pkg/clients/engineer"
	"github.com/tidbcloud/jiraport/pkg/clients/lambda"
	"github.com/tidbcloud/jiraport/pkg/config"
	"github.com/tidbcloud/jiraport/pkg/db"
	"github.com/tidbcloud/jiraport/pkg/matcher"
	ntf "github.com/tidbcloud/jiraport/pkg/resolver/notification"
)

type JiraEventHandler struct {
	notificationSvc *ntf.Notifications
	dbClient        db.Interface
	policyMatcher   *matcher.Matcher
	eventMatcher    *matcher.Matcher
	logger          *slog.Logger
	engClient       *engineer.Client
}

func New(
	logger *slog.Logger,
	dbClient db.Interface,
	notificationSvc *ntf.Notifications,
	engClient *engineer.Client,
) (*JiraEventHandler, error) {
	policyMatcher, err := matcher.NewMatcher(
		logger, viper.GetString("policy-matcher-file"))
	if err != nil {
		return nil, err
	}
	eventMatcher, err := matcher.NewMatcher(
		logger, viper.GetString("event-matcher-file"))
	if err != nil {
		return nil, err
	}

	return &JiraEventHandler{
		logger:          logger,
		notificationSvc: notificationSvc,
		policyMatcher:   policyMatcher,
		eventMatcher:    eventMatcher,
		dbClient:        dbClient,
		engClient:       engClient,
	}, nil
}

func (handler *JiraEventHandler) PrefillEvent(event *jira.JiraEvent) {
	if event.Issue.Fields.Assignee != nil {
		accountID := event.Issue.Fields.Assignee.AccountID
		if accountID != "" {
			email, err := handler.engClient.GetEmailByAccountID(accountID)
			if err != nil {
				handler.logger.Warn("fail to fetch email by account id", "account id", accountID)
				return
			}
			if *email != "" {
				event.Issue.Fields.Assignee.EmailAddress = *email
			}
		}
	}
}

func (handler *JiraEventHandler) Handle(ctx context.Context, ev lambda.Event) {
	var jiraEvent jira.JiraEvent
	err := json.Unmarshal(ev.Payload, &jiraEvent)
	if err != nil {
		handler.logger.Error("unexpected payload", "err", err, "payload", ev.Payload)
		return
	}
	handler.PrefillEvent(&jiraEvent)
	handler.logger.Info("receive jira event", "issue", jiraEvent.Issue.Key)

	policies, err := handler.matchPolicy(&jiraEvent)
	if err != nil {
		if err == errNoMatchedPolicy {
			handler.logger.Info("no policy matched", "issue", jiraEvent.Issue.Key)
		} else {
			handler.logger.Error("match policy error", "err", err, "issue", jiraEvent.Issue.Key)
			return
		}
	}

	issue, err := jiraEvent.ToIssue()
	if err != nil {
		handler.logger.Error("translate to issue error", "err", err, "issue", jiraEvent.Issue.Key)
		return
	}
	err = handler.dbClient.CreateIssue(issue)
	if err != nil {
		handler.logger.Error("create issue error", "err", err, "issue", jiraEvent.Issue.Key)
		return
	}

	for _, policy := range policies {
		handler.logger.Info("create notification",
			"policy", policy,
			"issue", jiraEvent.Issue.Key)
		err := handler.dbClient.CreateNotification(policy, issue)
		if err != nil {
			handler.logger.Error("create notification error", "err", err, "issue", jiraEvent.Issue.Key, "policy", policy)
			return
		}
	}

	event, err := handler.matchEvent(&jiraEvent)
	if err != nil {
		handler.logger.Warn("match event error", "err", err, "issue", jiraEvent.Issue.Key)
		return
	}
	handler.logger.Info("matched event", "event", *event, "issue", jiraEvent.Issue.Key)

	err = handler.notificationSvc.GlobalTransitionWithUpdate(issue, *event)
	err = ntf.HandleTransitionError(err, func(te *db.TransitionError) error {
		handler.logger.Warn("do global transition error part", "err", te, "issue", jiraEvent.Issue.Key)
		return nil
	})
	if err != nil {
		handler.logger.Error("do global transition error", "err", err, "issue", jiraEvent.Issue.Key)
		return
	}
	handler.logger.Info("handle jira event success", "issue", jiraEvent.Issue.Key)
}

var errNoMatchedPolicy = errors.New("no matched policy")

func (handler *JiraEventHandler) matchPolicy(jev *jira.JiraEvent) ([]string, error) {
	policyReceivers, err := handler.policyMatcher.Match(jev)
	if err != nil {
		return nil, fmt.Errorf("policy matching error, %w", err)
	}
	policies, err := policyReceivers.TryToStringSlice()
	if err != nil {
		return nil, err
	}
	if len(policies) == 0 {
		return nil, errNoMatchedPolicy
	}
	return policies, nil
}

var errNoMatchedEvent = errors.New("no matched event")

func (handler *JiraEventHandler) matchEvent(jev *jira.JiraEvent) (*config.NotificationTransitionEvent, error) {
	eventReceiver, err := handler.eventMatcher.Match(jev)
	if err != nil {
		return nil, fmt.Errorf("event matching error, %w", err)
	}
	events, err := eventReceiver.TryToStringSlice()
	if err != nil {
		return nil, err
	}
	if len(events) == 0 {
		return nil, errNoMatchedEvent
	}
	return (*config.NotificationTransitionEvent)(&events[0]), nil
}
