package webhook

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"strings"

	log "github.com/sirupsen/logrus"
)

// ghcrParser parses webhook payloads sent by GitHub Container Registry (GHCR).
//
// It extracts container image publication events from GitHub package webhooks
// and converts them into a normalized RegistryEvent structure.
type ghcrParser struct {
	secret string
}

// GHCRPayload represents the webhook payload sent by GitHub for
// package events.
type GHCRPayload struct {
	Action  string `json:"action"`
	Package struct {
		Name        string `json:"name"`
		PackageType string `json:"package_type"`
		Owner       struct {
			Login string `json:"login"`
		} `json:"owner"`
		PackageVersion struct {
			ContainerMetadata struct {
				Tag struct {
					Name string `json:"name"`
				} `json:"tag"`
			} `json:"container_metadata"`
		} `json:"package_version"`
	} `json:"package"`
}

// newGHCRParser creates a new ghcrParser instance.
//
// The parser supports GitHub package webhook events for container images
// published to GitHub Container Registry (ghcr.io).
func newGHCRParser(secret string) *ghcrParser {
	if secret == "" {
		log.Warn("GHCR webhook secret is not configured; incoming webhook events will not be validated")
	}
	return &ghcrParser{secret: secret}
}

// CanHandle reports whether the HTTP request corresponds to a GHCR webhook.
//
// It checks the GitHub event header and returns true for package-related
// events that may contain container registry updates.
func (p *ghcrParser) CanHandle(r *http.Request) bool {
	return r.Header.Get("X-GitHub-Event") == "package"
}

// Parse validates the request signature and extracts container publication
// details from a GHCR webhook payload.
//
// The method expects a GitHub package event with action "published" for a
// container package. It returns a normalized RegistryEvent containing the
// registry host, repository, and tag. Returns nil, nil for events that are
// intentionally skipped (unsupported actions, non-container packages, or
// missing tags). Only returns an error for genuinely malformed payloads or
// signature verification failures.
func (p *ghcrParser) Parse(r *http.Request) (any, error) {
	body, err := io.ReadAll(r.Body)
	if err != nil {
		return nil, err
	}
	if err := p.validateSignature(r, body); err != nil {
		return nil, err
	}
	var payload GHCRPayload
	if err := json.Unmarshal(body, &payload); err != nil {
		return nil, fmt.Errorf("failed to unmarshal GHCR webhook payload: %w", err)
	}

	if payload.Action != "published" {
		log.Debugf("Skipping GHCR webhook event: unsupported action %q", payload.Action)
		return nil, nil
	}

	if !strings.EqualFold(payload.Package.PackageType, "container") {
		log.Debugf("Skipping GHCR webhook event: unsupported package type %q", payload.Package.PackageType)
		return nil, nil
	}

	repository := payload.Package.Owner.Login + "/" + payload.Package.Name
	tag := payload.Package.PackageVersion.ContainerMetadata.Tag.Name

	if tag == "" {
		log.Debugf("Skipping GHCR webhook event: missing tag for repository %q", repository)
		return nil, nil
	}

	return &RegistryEvent{
		RegistryURL: "ghcr.io",
		Repository:  repository,
		Tag:         tag,
	}, nil
}

// validateSignature verifies the webhook request signature using HMAC-SHA256.
//
// If a secret is configured, the method checks the X-Hub-Signature-256 header
// against the computed signature of the request body. An error is returned if
// the signature is missing or does not match. If no secret is configured,
// validation is skipped.
func (p *ghcrParser) validateSignature(r *http.Request, body []byte) error {
	if p.secret != "" {
		signature := r.Header.Get("X-Hub-Signature-256")
		if signature == "" {
			return fmt.Errorf("%w: missing X-Hub-Signature-256 header", ErrHMACVerificationFailed)
		}

		mac := hmac.New(sha256.New, []byte(p.secret))
		mac.Write(body)
		expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))

		if !hmac.Equal([]byte(signature), []byte(expected)) {
			return fmt.Errorf("%w: signature mismatch", ErrHMACVerificationFailed)
		}
	}

	return nil
}
