package webhook

import (
	"bytes"
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/url"
	"strconv"
	"strings"
	"time"
)

type GiteaUser struct {
	Login string `json:"login"`
}

type GiteaWorkflow struct {
	Name string `json:"name"`
}

type GiteaWorkflowRun struct {
	ID         int64  `json:"id"`
	Name       string `json:"name"`
	Status     string `json:"status"`
	Conclusion string `json:"conclusion"`
	HTMLURL    string `json:"html_url"`
	HeadSHA    string `json:"head_sha"`
}

type GiteaWorkflowJob struct {
	ID         int64  `json:"id"`
	Name       string `json:"name"`
	Status     string `json:"status"`
	Conclusion string `json:"conclusion"`
	HTMLURL    string `json:"html_url"`
	HeadSHA    string `json:"head_sha"`
}

type GiteaCommit struct {
	Added    []string `json:"added"`
	Modified []string `json:"modified"`
	Removed  []string `json:"removed"`
}

type GiteaPayload struct {
	Action     string    `json:"action"`
	Sender     GiteaUser `json:"sender"`
	Repository struct {
		FullName      string `json:"full_name"`
		Name          string `json:"name"`
		DefaultBranch string `json:"default_branch"`
		Owner         struct {
			Login string `json:"login"`
		} `json:"owner"`
	} `json:"repository"`
	PullRequest struct {
		Number    int64     `json:"number"`
		HTMLURL   string    `json:"html_url"`
		Merged    bool      `json:"merged"`
		UpdatedAt time.Time `json:"updated_at"`
		ClosedAt  time.Time `json:"closed_at"`
		MergedAt  time.Time `json:"merged_at"`
		Base      struct {
			Ref string `json:"ref"`
			SHA string `json:"sha"`
		} `json:"base"`
		Head struct {
			Ref string `json:"ref"`
			SHA string `json:"sha"`
		} `json:"head"`
	} `json:"pull_request"`
	Issue struct {
		Number      int64  `json:"number"`
		HTMLURL     string `json:"html_url"`
		PullRequest *struct {
			HTMLURL string `json:"html_url"`
		} `json:"pull_request"`
	} `json:"issue"`
	Review struct {
		ID          int64     `json:"id"`
		Type        string    `json:"type"`
		Body        string    `json:"body"`
		HTMLURL     string    `json:"html_url"`
		SubmittedAt time.Time `json:"submitted_at"`
		User        GiteaUser `json:"user"`
	} `json:"review"`
	Comment struct {
		ID        int64     `json:"id"`
		Body      string    `json:"body"`
		Path      string    `json:"path"`
		Line      int64     `json:"line"`
		HTMLURL   string    `json:"html_url"`
		CreatedAt time.Time `json:"created_at"`
		UpdatedAt time.Time `json:"updated_at"`
		User      GiteaUser `json:"user"`
	} `json:"comment"`
	Workflow    GiteaWorkflow    `json:"workflow"`
	WorkflowRun GiteaWorkflowRun `json:"workflow_run"`
	WorkflowJob GiteaWorkflowJob `json:"workflow_job"`
	Commits     []GiteaCommit    `json:"commits"`
	Ref         string           `json:"ref"`
	Before      string           `json:"before"`
	After       string           `json:"after"`
	CompareURL  string           `json:"compare_url"`
	SHA         string           `json:"sha"`
	State       string           `json:"state"`
	Context     string           `json:"context"`
	TargetURL   string           `json:"target_url"`
	Description string           `json:"description"`
	CreatedAt   time.Time        `json:"created_at"`
	UpdatedAt   time.Time        `json:"updated_at"`
}

func ExtractGiteaConnectionRefs(payload []byte) (string, string, bool) {
	if len(payload) == 0 {
		return "", "", false
	}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	var envelope struct {
		Repository struct {
			FullName string `json:"full_name"`
			Name     string `json:"name"`
			Owner    struct {
				Login string `json:"login"`
			} `json:"owner"`
		} `json:"repository"`
	}
	if err := decoder.Decode(&envelope); err != nil {
		return "", "", false
	}
	fullName := strings.ToLower(strings.TrimSpace(envelope.Repository.FullName))
	owner := strings.ToLower(strings.TrimSpace(envelope.Repository.Owner.Login))
	name := strings.ToLower(strings.TrimSpace(envelope.Repository.Name))
	if fullName == "" && owner != "" && name != "" {
		fullName = owner + "/" + name
	}
	if fullName == "" && owner == "" {
		return "", "", false
	}
	return fullName, owner, true
}

func VerifyGiteaWebhookSignature(signatureHeader string, payload []byte, secret string) bool {
	trimmedSignature := strings.TrimSpace(signatureHeader)
	trimmedSecret := strings.TrimSpace(secret)
	if trimmedSignature == "" || trimmedSecret == "" {
		return false
	}
	trimmedSignature = strings.TrimPrefix(strings.ToLower(trimmedSignature), "sha256=")

	mac := hmac.New(sha256.New, []byte(trimmedSecret))
	_, _ = mac.Write(payload)
	expected := hex.EncodeToString(mac.Sum(nil))
	return hmac.Equal([]byte(trimmedSignature), []byte(expected))
}

func GiteaWebhookDeliveryKey(deliveryHeader string, eventType string, payload []byte) string {
	return DeliveryKey(deliveryHeader, eventType, payload)
}

func ParseGiteaWebhookPayload(payload []byte) (GiteaPayload, bool, error) {
	if len(payload) == 0 {
		return GiteaPayload{}, false, nil
	}
	decoder := json.NewDecoder(bytes.NewReader(payload))
	decoder.UseNumber()
	var event GiteaPayload
	if err := decoder.Decode(&event); err != nil {
		return GiteaPayload{}, false, fmt.Errorf("decode gitea webhook payload: %w", err)
	}
	var trailing struct{}
	if err := decoder.Decode(&trailing); err != io.EOF {
		if err == nil {
			return GiteaPayload{}, false, fmt.Errorf("decode gitea webhook payload: multiple JSON values")
		}
		return GiteaPayload{}, false, fmt.Errorf("decode gitea webhook payload: %w", err)
	}
	return event, true, nil
}

func NormalizeGiteaWebhookEventType(eventHeader string, action string) string {
	switch strings.TrimSpace(strings.ToLower(eventHeader)) {
	case "pull_request":
		switch strings.TrimSpace(strings.ToLower(action)) {
		case "synchronized", "synchronize":
			return "pull_request_synchronize"
		case "closed":
			return "pull_request_closed"
		case "reopened":
			return "pull_request_reopened"
		default:
			return "pull_request"
		}
	case "pull_request_review":
		return "pull_request_review_submitted"
	case "pull_request_comment":
		return "pull_request_review_comment_created"
	case "issue_comment":
		if strings.EqualFold(strings.TrimSpace(action), "created") {
			return "issue_comment_created"
		}
		return "issue_comment"
	case "push":
		return "push"
	case "status":
		return "status"
	case "workflow_run":
		return "workflow_run"
	case "workflow_job":
		return "workflow_job"
	default:
		return strings.TrimSpace(strings.ToLower(eventHeader))
	}
}

func GiteaPullRequestResourceFromPullRequest(event GiteaPayload, instanceURL string) (PullRequestResource, bool) {
	return GiteaPullRequestResourceFromNumber(
		event.Repository.FullName,
		event.PullRequest.Number,
		event.PullRequest.HTMLURL,
		instanceURL,
	)
}

func GiteaPullRequestResourceFromIssueComment(event GiteaPayload, instanceURL string) (PullRequestResource, bool, bool) {
	if event.Issue.PullRequest == nil {
		return PullRequestResource{}, false, false
	}
	resourceURL := event.Issue.HTMLURL
	if strings.TrimSpace(resourceURL) == "" {
		resourceURL = event.Issue.PullRequest.HTMLURL
	}
	resource, ok := GiteaPullRequestResourceFromNumber(
		event.Repository.FullName,
		event.Issue.Number,
		resourceURL,
		instanceURL,
	)
	return resource, ok, true
}

func GiteaPullRequestResourceFromNumber(repositoryFullName string, number int64, resourceURL string, instanceURL string) (PullRequestResource, bool) {
	normalizedRepository := strings.ToLower(strings.TrimSpace(repositoryFullName))
	if normalizedRepository == "" || number <= 0 {
		return PullRequestResource{}, false
	}

	trimmedResourceURL := strings.TrimSpace(resourceURL)
	if trimmedResourceURL == "" {
		trimmedResourceURL = BuildGiteaPullRequestURL(instanceURL, normalizedRepository, int(number))
	}
	if trimmedResourceURL == "" {
		return PullRequestResource{}, false
	}

	return PullRequestResource{
		ResourceType:       "gitea_pull_request",
		ResourceIdentifier: fmt.Sprintf("%s#%d", normalizedRepository, number),
		ResourceURL:        trimmedResourceURL,
		RepositoryFullName: normalizedRepository,
		Number:             int(number),
	}, true
}

func BuildGiteaPullRequestURL(instanceURL string, repositoryFullName string, number int) string {
	baseURL, err := url.Parse(strings.TrimSpace(instanceURL))
	if err != nil || strings.TrimSpace(baseURL.Host) == "" {
		return ""
	}
	parts := strings.Split(strings.TrimSpace(repositoryFullName), "/")
	if len(parts) != 2 {
		return ""
	}
	baseSegments := SplitPathSegments(baseURL.Path)
	baseURL.RawQuery = ""
	baseURL.Fragment = ""
	baseURL.Path = "/" + strings.Join(append(baseSegments, parts[0], parts[1], "pulls", strconv.Itoa(number)), "/")
	return strings.TrimRight(baseURL.String(), "/")
}

func SplitPathSegments(path string) []string {
	trimmed := strings.TrimSpace(path)
	if trimmed == "" {
		return nil
	}
	segments := strings.Split(strings.Trim(trimmed, "/"), "/")
	result := make([]string, 0, len(segments))
	for _, segment := range segments {
		part := strings.TrimSpace(segment)
		if part == "" {
			continue
		}
		result = append(result, part)
	}
	return result
}

func NormalizeGiteaReviewState(value string) string {
	switch strings.TrimSpace(strings.ToLower(value)) {
	case "approve", "approved":
		return "approved"
	case "reject", "rejected", "request_changes", "changes_requested":
		return "changes_requested"
	case "comment", "commented":
		return "commented"
	default:
		return strings.TrimSpace(strings.ToLower(value))
	}
}
