package client

import (
	"io"
	"time"

	"github.com/andygrunwald/go-jira"
	"github.com/go-kit/kit/log"
	"github.com/go-kit/kit/log/level"
	"github.com/pkg/errors"
	jiraext "github.com/prometheus-community/jiralert/pkg/jira"
	"github.com/trivago/tgo/tcontainer"
)

type JiraAdapter struct {
	client jiraext.JiraIssueService
	logger log.Logger
}

func NewJiraAdapter(client jiraext.JiraIssueService, logger log.Logger) *JiraAdapter {
	return &JiraAdapter{
		client: client,
		logger: logger,
	}
}

func (j *JiraAdapter) handleJiraErrResponse(api string, resp *jira.Response, err error) (bool, error) {
	if resp == nil || resp.Request == nil {
		level.Debug(j.logger).Log("msg", "handleJiraErrResponse", "api", api, "err", err)
	} else {
		level.Debug(j.logger).Log("msg", "handleJiraErrResponse", "api", api, "err", err, "url", resp.Request.URL)
	}

	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, errors.Errorf("JIRA request %s returned status %s, body %q", resp.Request.URL, resp.Status, string(body))
	}
	return false, errors.Wrapf(err, "JIRA request %s failed", api)
}

func (j *JiraAdapter) mapToGeneric(jiraIssue *jira.Issue) *Issue {
	if jiraIssue == nil {
		return nil
	}

	issue := &Issue{
		ID:           jiraIssue.ID,
		Key:          jiraIssue.Key,
		Raw:          jiraIssue,
		CustomFields: make(map[string]interface{}),
	}

	if jiraIssue.Fields != nil {
		issue.Summary = jiraIssue.Fields.Summary
		issue.Description = jiraIssue.Fields.Description
		issue.Project = jiraIssue.Fields.Project.Key
		issue.IssueType = jiraIssue.Fields.Type.Name
		issue.Labels = jiraIssue.Fields.Labels
		if jiraIssue.Fields.Priority != nil {
			issue.Priority = jiraIssue.Fields.Priority.Name
		}
		if jiraIssue.Fields.Resolution != nil {
			issue.Resolution = jiraIssue.Fields.Resolution.Name
		}
		issue.ResolutionDate = time.Time(jiraIssue.Fields.Resolutiondate)
		issue.CreatedDate = time.Time(jiraIssue.Fields.Created)
		if jiraIssue.Fields.Status != nil {
			issue.Status = jiraIssue.Fields.Status.Name
			issue.StatusCategory = jiraIssue.Fields.Status.StatusCategory.Key
		}
		if jiraIssue.Fields.Parent != nil {
			issue.ParentKey = jiraIssue.Fields.Parent.Key
		}
		for _, comp := range jiraIssue.Fields.Components {
			issue.Components = append(issue.Components, comp.Name)
		}

		for k, v := range jiraIssue.Fields.Unknowns {
			issue.CustomFields[k] = v
		}
	}

	return issue
}

func (j *JiraAdapter) mapToJira(issue *Issue) *jira.Issue {
	if issue == nil {
		return nil
	}

	jiraIssue := &jira.Issue{
		Key: issue.Key,
		Fields: &jira.IssueFields{
			Project:     jira.Project{Key: issue.Project},
			Type:        jira.IssueType{Name: issue.IssueType},
			Summary:     issue.Summary,
			Description: issue.Description,
			Labels:      issue.Labels,
			Unknowns:    tcontainer.NewMarshalMap(),
		},
	}

	if issue.ParentKey != "" {
		jiraIssue.Fields.Parent = &jira.Parent{Key: issue.ParentKey}
	}
	if issue.Priority != "" {
		jiraIssue.Fields.Priority = &jira.Priority{Name: issue.Priority}
	}
	if len(issue.Components) > 0 {
		for _, comp := range issue.Components {
			jiraIssue.Fields.Components = append(jiraIssue.Fields.Components, &jira.Component{Name: comp})
		}
	}

	for k, v := range issue.CustomFields {
		jiraIssue.Fields.Unknowns[k] = v
	}

	return jiraIssue
}

func (j *JiraAdapter) Search(query string, options *SearchOptions) ([]*Issue, bool, error) {
	jiraOpts := &jira.SearchOptions{
		Fields:     []string{"summary", "status", "resolution", "resolutiondate", "description", "created"},
		MaxResults: 2,
	}
	if options != nil && options.MaxResults > 0 {
		jiraOpts.MaxResults = options.MaxResults
	}

	jiraIssues, resp, err := j.client.Search(query, jiraOpts)
	if err != nil {
		retry, err := j.handleJiraErrResponse("Issue.Search", resp, err)
		return nil, retry, err
	}

	results := make([]*Issue, 0, len(jiraIssues))
	for _, ji := range jiraIssues {
		jiCopy := ji // avoid pointer sharing
		results = append(results, j.mapToGeneric(&jiCopy))
	}
	return results, false, nil
}

func (j *JiraAdapter) Create(issue *Issue) (*Issue, bool, error) {
	jiraIssue := j.mapToJira(issue)
	newJiraIssue, resp, err := j.client.Create(jiraIssue)
	if err != nil {
		retry, err := j.handleJiraErrResponse("Issue.Create", resp, err)
		return nil, retry, err
	}
	return j.mapToGeneric(newJiraIssue), false, nil
}

func (j *JiraAdapter) UpdateSummary(issueKey string, summary string) (bool, error) {
	issueUpdate := &jira.Issue{
		Key: issueKey,
		Fields: &jira.IssueFields{
			Summary: summary,
		},
	}
	_, resp, err := j.client.UpdateWithOptions(issueUpdate, nil)
	if err != nil {
		return j.handleJiraErrResponse("Issue.UpdateWithOptions", resp, err)
	}
	return false, nil
}

func (j *JiraAdapter) UpdateDescription(issueKey string, description string) (bool, error) {
	issueUpdate := &jira.Issue{
		Key: issueKey,
		Fields: &jira.IssueFields{
			Description: description,
		},
	}
	_, resp, err := j.client.UpdateWithOptions(issueUpdate, nil)
	if err != nil {
		return j.handleJiraErrResponse("Issue.UpdateWithOptions", resp, err)
	}
	return false, nil
}

func (j *JiraAdapter) AddComment(issueKey string, body string) (bool, error) {
	comment := jira.Comment{Body: body}
	_, resp, err := j.client.AddComment(issueKey, &comment)
	if err != nil {
		return j.handleJiraErrResponse("Issue.AddComment", resp, err)
	}
	return false, nil
}

func (j *JiraAdapter) AutoRecover(issueKey string, recoveredMsg string) (bool, error) {
	unknowns := make(tcontainer.MarshalMap)
	unknowns["customfield_10256"] = recoveredMsg
	issueUpdate := &jira.Issue{
		Key: issueKey,
		Fields: &jira.IssueFields{
			Unknowns: unknowns,
		},
	}
	_, resp, err := j.client.UpdateWithOptions(issueUpdate, nil)
	if err != nil {
		return j.handleJiraErrResponse("Issue.UpdateWithOptions", resp, err)
	}
	return false, nil
}
