package jira

import (
	"context"
	"errors"
	"fmt"

	"github.com/spf13/viper"

	"jiraport/pkg/log"
	"jiraport/pkg/matcher"
	"jiraport/pkg/queue"
	"jiraport/service/jira"
	"jiraport/service/lark"
	ntf "jiraport/service/notification"
	"jiraport/service/timer"
)

type JiraEventHandler struct {
	lark          *lark.Lark
	timer         *timer.Timer
	ns            *ntf.Notifications
	policyMatcher matcher.Matcher
	eventMatcher  matcher.Matcher
}

func New(
	lark *lark.Lark,
	timer *timer.Timer,
	ns *ntf.Notifications) (*JiraEventHandler, error) {

	var policyMatcher matcher.Matcher
	err := policyMatcher.LoadFile(viper.GetString("policy-matcher-file"))
	if err != nil {
		return nil, err
	}
	log.Debug("load policy matcher", policyMatcher)

	var eventMatcher matcher.Matcher
	err = eventMatcher.LoadFile(viper.GetString("event-matcher-file"))
	if err != nil {
		return nil, err
	}
	log.Debug("load event matcher", eventMatcher)

	return &JiraEventHandler{
		lark:          lark,
		timer:         timer,
		ns:            ns,
		policyMatcher: policyMatcher,
		eventMatcher:  eventMatcher,
	}, nil
}

func (handler *JiraEventHandler) Handle(ctx context.Context, ev queue.Event) {
	log.Debug("handle", ev.Name)
	jiraEvent, ok := ev.Payload.(*jira.JiraEvent)
	if !ok {
		log.Error("msg", "unexpected payload", "payload", ev.Payload)
		return
	}
	policies, err := handler.matchPolicy(jiraEvent)
	if err != nil {
		if err == errNoMatchedPolicy {
			log.Debug("msg", "no policy matched")
			return
		} else {
			log.Error("msg", "match policy error", "err", err)
			return
		}
	}

	log.Debug("match policies", policies, "issue", jiraEvent.Issue.Key)

	issue, err := jiraEvent.ToIssue()
	if err != nil {
		log.Error("msg", "translate to issue error", "err", err)
		return
	}

	err = handler.ns.Insert(issue)
	if err != nil {
		log.Error("msg", "insert issue error", "err", err)
		return
	}

	for _, policy := range policies {
		_, err := handler.ns.NewNotification(policy, issue)
		if err != nil {
			log.Error("msg", "creating notification error", "err", err)
			return
		}
	}

	event, err := handler.matchEvent(jiraEvent)
	if err != nil {
		log.Error("msg", "match event error", "err", err)
		return
	}

	err = handler.ns.GlobalTransitionWithUpdate(issue, *event)
	err = ntf.HandleTransitionError(err, func(te *ntf.TransitionError) error {
		log.Warn("msg", "do global transition error part", "err", te)
		return nil
	})
	if err != nil {
		log.Error("msg", "do global transition error", "err", err)
	}
}

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) (*ntf.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 (*ntf.NotificationTransitionEvent)(&events[0]), nil
}
