package webhook

import (
	"encoding/json"
	"errors"
	"io"
	"log/slog"
	"net/http"
	"strings"

	apicontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/api"
	webhookcontract "bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/contracts/webhook"
	"bit.admedia.com/scm/ad/adpilot-indexing-repo-sync.com/internal/service"
)

const maxBodyBytes = 1 << 20 // 1 MiB

// SyncTrigger accepts repository sync requests for background processing.
type SyncTrigger interface {
	TriggerSync(repoID, ref string) (jobID string, err error)
}

// Handler serves Bitbucket Server webhook requests.
type Handler struct {
	secret      string
	environment string
	sync        SyncTrigger
	dedupe      *Dedupe
	logger      *slog.Logger
}

// NewHandler creates a Bitbucket webhook HTTP handler.
func NewHandler(secret, environment string, sync SyncTrigger, logger *slog.Logger) *Handler {
	if logger == nil {
		logger = slog.Default()
	}
	return &Handler{
		secret:      secret,
		environment: environment,
		sync:        sync,
		dedupe:      NewDedupe(defaultDedupeTTL),
		logger:      logger,
	}
}

// ServeHTTP implements http.Handler.
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		writeError(w, http.StatusMethodNotAllowed, "method not allowed")
		return
	}

	rawBody, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxBodyBytes))
	if err != nil {
		writeError(w, http.StatusBadRequest, "invalid request body")
		return
	}

	if err := h.verifyRequest(rawBody, r.Header.Get("X-Hub-Signature")); err != nil {
		if errors.Is(err, ErrMissingSignature) || errors.Is(err, ErrInvalidSignature) {
			writeError(w, http.StatusUnauthorized, "invalid signature")
			return
		}
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}

	eventKey := EventKeyFromHeader(r.Header.Get("X-Event-Key"))
	if eventKey != webhookcontract.EventRefsChanged {
		writeWebhook(w, apicontract.WebhookResponse{
			Status: "ignored",
			Reason: "unsupported event",
		})
		return
	}

	parsed, err := ParseRefsChanged(rawBody, eventKey)
	if err != nil {
		writeError(w, http.StatusBadRequest, err.Error())
		return
	}

	if len(parsed.Master) == 0 {
		writeWebhook(w, apicontract.WebhookResponse{
			Status: "ignored",
			Reason: "not master branch",
			RepoID: parsed.RepoID,
		})
		return
	}

	commitSHA := latestToHash(parsed.Master)
	if commitSHA == "" {
		writeError(w, http.StatusBadRequest, "missing commit hash in master change")
		return
	}

	if !h.dedupe.TryRecord(parsed.RepoID, commitSHA) {
		writeWebhook(w, apicontract.WebhookResponse{
			Status: "ignored",
			Reason: "duplicate commit",
			RepoID: parsed.RepoID,
			Ref:    service.DefaultRef,
		})
		return
	}

	jobID, err := h.sync.TriggerSync(parsed.RepoID, service.DefaultRef)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "failed to enqueue sync")
		return
	}

	h.logger.Info("webhook accepted",
		"repo_id", parsed.RepoID,
		"ref", service.DefaultRef,
		"commit_sha", commitSHA,
		"job_id", jobID,
		"request_id", r.Header.Get("X-Request-Id"),
	)

	writeWebhook(w, apicontract.WebhookResponse{
		Status: "accepted",
		RepoID: parsed.RepoID,
		Ref:    service.DefaultRef,
		JobID:  jobID,
	})
}

func (h *Handler) verifyRequest(rawBody []byte, signatureHeader string) error {
	secret := strings.TrimSpace(h.secret)
	if secret == "" {
		if strings.EqualFold(strings.TrimSpace(h.environment), "local") {
			h.logger.Warn("webhook signature verification skipped: empty secret in local environment")
			return nil
		}
		return ErrMissingSignature
	}
	return VerifySignature(secret, rawBody, signatureHeader)
}

func latestToHash(changes []webhookcontract.RefChange) string {
	if len(changes) == 0 {
		return ""
	}
	return strings.TrimSpace(changes[len(changes)-1].ToHash)
}

func writeError(w http.ResponseWriter, status int, message string) {
	writeJSON(w, status, map[string]string{"error": message})
}

func writeWebhook(w http.ResponseWriter, payload apicontract.WebhookResponse) {
	writeJSON(w, http.StatusAccepted, payload)
}

func writeJSON(w http.ResponseWriter, status int, payload any) {
	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(status)
	_ = json.NewEncoder(w).Encode(payload)
}
