package jira

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"

	jira "github.com/andygrunwald/go-jira"
	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"

	"jiraport/pkg/log"
	"jiraport/pkg/queue"
)

type Jira struct {
	queue      chan<- queue.Event
	client     *jira.Client
	mode       string
	needAssign bool
}

type JiraEvent struct {
	JiraWebhookEvent
	Receiver string
}

// create new jira service and register route in gin router
func New(queue chan<- queue.Event, r *gin.RouterGroup) (*Jira, error) {
	mode := viper.GetString("mode")
	needAssign := viper.GetBool("need-assign")

	var client *jira.Client
	var err error

	// only connect jira when need assigning
	if needAssign {
		baseUrl := viper.GetString("jira-base-url")
		token := viper.GetString("jira-token")

		tp := jira.PATAuthTransport{
			Token: token,
		}
		client, err = jira.NewClient(tp.Client(), baseUrl)
		if err != nil {
			return nil, err
		}
	}

	j := &Jira{queue: queue, client: client, mode: mode, needAssign: needAssign}

	r.POST("/webhook/:receiver", j.jiraEventHandler())

	return j, nil
}

func handleJiraErrResponse(api string, resp *jira.Response, err error) (bool, error) {
	if resp != nil && resp.StatusCode/100 != 2 {
		retry := resp.StatusCode == 500 || resp.StatusCode == 502 || resp.StatusCode == 503 || resp.StatusCode == 504
		body, _ := io.ReadAll(resp.Body)
		return retry, fmt.Errorf("JIRA request %s returned status %s, body %q", resp.Request.URL, resp.Status, string(body))
	}
	if err != nil {
		return false, fmt.Errorf("JIRA request %s failed: %w", api, err)
	}
	return false, nil
}

func (j *Jira) GetIssue(issueKey string) (*RawIssue, error) {
	issue, resp, err := j.client.Issue.Get(issueKey, nil)
	_, err = handleJiraErrResponse("Issue.Get", resp, err)
	if err != nil {
		return nil, err
	}
	return (*RawIssue)(issue), nil
}

func (j *Jira) Assign(issueKey string, assignee string) error {
	if !j.needAssign {
		return errors.New("\"need-assign\" is not set, assiging is not allowed")
	}
	user, err := j.getUserByName(assignee)
	if err != nil {
		return err
	}
	if j.mode == "debug" {
		log.Debug("debug msg", "assign", "issue", issueKey, "user", user.EmailAddress)
		return nil
	}
	log.Debug("msg", "assign issue", "issue", issueKey, "assignee", assignee)
	return j.assignUser(issueKey, user)
}

func (j *Jira) assignUser(issueKey string, assignee *jira.User) error {
	resp, err := j.client.Issue.UpdateAssignee(issueKey, assignee)
	_, err = handleJiraErrResponse("Issue.UpdateAssignee", resp, err)
	if err != nil {
		return err
	}
	return nil
}

// search user by username
func (j *Jira) getUserByName(username string) (*jira.User, error) {
	relUrl, err := url.Parse("/rest/api/2/user")
	if err != nil {
		return nil, err
	}
	param := url.Values{}
	param.Add("username", username)
	relUrl.RawQuery = param.Encode()
	req, _ := j.client.NewRequest("GET", relUrl.String(), nil)
	user := &jira.User{}
	resp, err := j.client.Do(req, user)
	_, err = handleJiraErrResponse("User.Get", resp, err)
	if err != nil {
		return nil, err
	}
	return user, nil
}

func (j *Jira) AddComment(issueKey string, body string) error {
	if j.mode == "debug" {
		log.Debug("debug msg", "add comment", "comment", body)
		return nil
	}
	comment := jira.Comment{Body: body}
	_, resp, err := j.client.Issue.AddComment(issueKey, &comment)
	_, err = handleJiraErrResponse("Issue.AddComment", resp, err)
	if err != nil {
		return err
	}
	log.Debug("msg", "comment added", "key", issueKey)
	return nil
}

// gin route handler, receive http post from jira and send event to resolver
func (j *Jira) jiraEventHandler() func(*gin.Context) {
	return func(ctx *gin.Context) {
		receiver := ctx.Param("receiver")

		var jiraWebhookEvent JiraWebhookEvent
		err := ctx.ShouldBindJSON(&jiraWebhookEvent)
		if err != nil {
			log.Error("msg", err)
			ctx.Status(http.StatusBadRequest)
			return
		}

		log.Debug("receiver", receiver, "raw event", &jiraWebhookEvent, "more fields", &jiraWebhookEvent.Issue.Fields.Unknowns)
		log.Info("receiver", receiver, "webhook_event", jiraWebhookEvent.WebhookEvent, "issue", jiraWebhookEvent.Issue.Key)

		jiraEvent := &JiraEvent{jiraWebhookEvent, receiver}

		event := queue.Event{
			Name:    "JiraIssue",
			Payload: jiraEvent,
		}
		j.queue <- event

		ctx.Status(http.StatusOK)
	}
}
