// Copyright (c) 2026 Alibaba Group and its affiliates

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

//     http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package transparency

import (
	"bytes"
	"context"
	"crypto/ecdsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"fmt"
	"io"
	"net/http"
	"strings"
	"time"

	merkleproof "github.com/transparency-dev/merkle/proof"
	"github.com/transparency-dev/merkle/rfc6962"

	"github.com/dashscope/dashscope-confidential-maas/pkg/utils"
)

const LogTypeRekorV1 LogType = "rekor-v1"

// RekorV1PubKeyPEM is the PEM-encoded public key of the default Rekor v1 instance,
// used for checkpoint and SET verification.
const RekorV1PubKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE2G2Y+2tabdTV5BcGiBIx0a9fAFwr
kBbmLSGtks4L3qX6yYY0zufBnhC8Ur/iy55GhWP/9A/bY2LhC30M9+RYtw==
-----END PUBLIC KEY-----`

// LogEntryPubKeyPEM is the PEM-encoded public key used for DSSE envelope signature
// verification.
const LogEntryPubKeyPEM = `-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsGjh0eIF22/JwEkRvU5KROvNsL/F
K6qP/kbKO0CoelOqRKJQuC9z0ruwyx12S94/m69+iaan0SKR1IJjIbbfHw==
-----END PUBLIC KEY-----`

// RekorV1ServiceConfig holds configuration for a Rekor v1 transparency log service instance.
type RekorV1ServiceConfig struct {
	LogURL            string // Rekor server URL (required for FetchProofEntry)
	RekorV1PubKeyPEM  string // checkpoint + SET verification key (empty = use default RekorV1PubKeyPEM)
	LogEntryPubKeyPEM string // DSSE envelope signature verification (empty = use default LogEntryPubKeyPEM)
}

type rekorV1Service struct {
	logURL         string
	rekorV1PubKey  *ecdsa.PublicKey
	logEntryPubKey *ecdsa.PublicKey
}

// Rekor v1 types
type RekorV1Data struct {
	LogURL     string      `json:"logUrl"`
	LogIndex   int64       `json:"logIndex"`
	RekorEntry *RekorEntry `json:"rekorEntry"`
}

type RekorEntry struct {
	Body           string             `json:"body"`
	IntegratedTime int64              `json:"integratedTime"`
	LogID          string             `json:"logID"`
	LogIndex       int64              `json:"logIndex"`
	Verification   *RekorVerification `json:"verification,omitempty"`
}

type RekorVerification struct {
	InclusionProof       *InclusionProof `json:"inclusionProof,omitempty"`
	SignedEntryTimestamp string          `json:"signedEntryTimestamp,omitempty"`
}

type InclusionProof struct {
	Checkpoint string   `json:"checkpoint"`
	Hashes     []string `json:"hashes"`
	LogIndex   int64    `json:"logIndex"`
	RootHash   string   `json:"rootHash"`
	TreeSize   int64    `json:"treeSize"`
}

type RekorBody struct {
	APIVersion string        `json:"apiVersion"`
	Kind       string        `json:"kind"`
	Spec       RekorBodySpec `json:"spec"`
}

type RekorBodySpec struct {
	PayloadHash RekorPayloadHash `json:"payloadHash"`
	Signatures  []RekorSignature `json:"signatures"`
}

type RekorPayloadHash struct {
	Algorithm string `json:"algorithm"`
	Value     string `json:"value"`
}

type RekorSignature struct {
	Signature string `json:"signature"`
	Verifier  string `json:"verifier"`
}

// newRekorV1Service creates a Rekor v1 TranslogService.
// config must be *RekorV1ServiceConfig (nil is not accepted).
func newRekorV1Service(config any) (TranslogService, error) {
	if config == nil {
		return nil, fmt.Errorf("transparency: rekor-v1 requires *RekorV1ServiceConfig, got nil")
	}
	cfg, ok := config.(*RekorV1ServiceConfig)
	if !ok {
		return nil, fmt.Errorf("transparency: rekor-v1 expects *RekorV1ServiceConfig, got %T", config)
	}

	rekorPEM := cfg.RekorV1PubKeyPEM
	if rekorPEM == "" {
		rekorPEM = RekorV1PubKeyPEM
	}
	rekorKey, err := parseECDSAPublicKeyPEM(rekorPEM)
	if err != nil {
		return nil, fmt.Errorf("transparency: parse rekorV1 public key: %w", err)
	}

	entryPEM := cfg.LogEntryPubKeyPEM
	if entryPEM == "" {
		entryPEM = LogEntryPubKeyPEM
	}
	entryKey, err := parseECDSAPublicKeyPEM(entryPEM)
	if err != nil {
		return nil, fmt.Errorf("transparency: parse log entry public key: %w", err)
	}

	return &rekorV1Service{
		logURL:         cfg.LogURL,
		rekorV1PubKey:  rekorKey,
		logEntryPubKey: entryKey,
	}, nil
}

func (s *rekorV1Service) FetchProofEntry(ctx context.Context, logIndex int64) (*ProofEntry, error) {
	if s.logURL == "" {
		return nil, fmt.Errorf("transparency: log URL is required")
	}
	entry, err := fetchRekorEntry(ctx, s.logURL, logIndex)
	if err != nil {
		return nil, fmt.Errorf("transparency: fetch Rekor entry: %w", err)
	}

	return &ProofEntry{
		Type: string(LogTypeRekorV1),
		Data: &RekorV1Data{LogURL: s.logURL, LogIndex: logIndex, RekorEntry: entry},
	}, nil
}

func fetchRekorEntry(ctx context.Context, logURL string, logIndex int64) (*RekorEntry, error) {
	url := fmt.Sprintf("%s/api/v1/log/entries?logIndex=%d", logURL, logIndex)
	client := &http.Client{Timeout: 30 * time.Second}

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}
	req.Header.Set("Accept", "application/json")

	resp, err := client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fetch Rekor entry: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		body, _ := io.ReadAll(resp.Body)
		return nil, fmt.Errorf("Rekor API %d: %s", resp.StatusCode, string(body))
	}

	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, fmt.Errorf("read response: %w", err)
	}

	var raw map[string]json.RawMessage
	if err := json.Unmarshal(body, &raw); err != nil {
		return nil, fmt.Errorf("parse response: %w", err)
	}
	for _, v := range raw {
		var entry RekorEntry
		if err := json.Unmarshal(v, &entry); err != nil {
			return nil, fmt.Errorf("parse entry: %w", err)
		}
		return &entry, nil
	}
	return nil, fmt.Errorf("no entries for logIndex %d", logIndex)
}

func (s *rekorV1Service) VerifyProofEntry(manifest *ReleaseManifest, entry *ProofEntry) error {
	dataBytes, err := json.Marshal(entry.Data)
	if err != nil {
		return fmt.Errorf("marshal proof data: %w", err)
	}
	var data RekorV1Data
	if err := json.Unmarshal(dataBytes, &data); err != nil {
		return fmt.Errorf("parse RekorV1Data: %w", err)
	}
	if data.RekorEntry == nil {
		return fmt.Errorf("rekorEntry is nil")
	}

	body, err := decodeRekorBody(data.RekorEntry)
	if err != nil {
		return err
	}

	if err := verifyPayloadHash(manifest, body.Spec.PayloadHash.Value); err != nil {
		return err
	}

	if err := s.verifyLogEntrySignature(manifest, body); err != nil {
		return err
	}

	if data.RekorEntry.Verification == nil || data.RekorEntry.Verification.InclusionProof == nil {
		return fmt.Errorf("inclusion proof is required but missing")
	}
	if err := s.verifyCheckpoint(data.RekorEntry.Verification.InclusionProof); err != nil {
		return fmt.Errorf("checkpoint: %w", err)
	}
	if err := s.verifyInclusionProof(data.RekorEntry); err != nil {
		return fmt.Errorf("inclusion proof: %w", err)
	}

	if data.RekorEntry.Verification.SignedEntryTimestamp != "" {
		if err := s.verifySET(data.RekorEntry); err != nil {
			return fmt.Errorf("SET: %w", err)
		}
	}

	return nil
}

func (s *rekorV1Service) VerifyPayloadHash(manifest *ReleaseManifest, entry *ProofEntry) error {
	dataBytes, err := json.Marshal(entry.Data)
	if err != nil {
		return fmt.Errorf("marshal proof data: %w", err)
	}
	var data RekorV1Data
	if err := json.Unmarshal(dataBytes, &data); err != nil {
		return fmt.Errorf("parse RekorV1Data: %w", err)
	}
	if data.RekorEntry == nil {
		return fmt.Errorf("rekorEntry is nil")
	}
	body, err := decodeRekorBody(data.RekorEntry)
	if err != nil {
		return err
	}
	return verifyPayloadHash(manifest, body.Spec.PayloadHash.Value)
}

func decodeRekorBody(entry *RekorEntry) (*RekorBody, error) {
	raw, err := base64.StdEncoding.DecodeString(entry.Body)
	if err != nil {
		return nil, fmt.Errorf("decode Rekor body: %w", err)
	}
	var body RekorBody
	if err := json.Unmarshal(raw, &body); err != nil {
		return nil, fmt.Errorf("parse Rekor body: %w", err)
	}
	if body.Kind != "dsse" {
		return nil, fmt.Errorf("unexpected body kind: %s (expected dsse)", body.Kind)
	}
	return &body, nil
}

func verifyPayloadHash(manifest *ReleaseManifest, expectedHex string) error {
	jcs, err := utils.JCSCanonicalize(manifest)
	if err != nil {
		return fmt.Errorf("JCS canonicalize: %w", err)
	}
	h := sha256.Sum256(jcs)
	got := fmt.Sprintf("%x", h)
	if got != expectedHex {
		return fmt.Errorf("payloadHash mismatch: %s != %s", got, expectedHex)
	}
	return nil
}

func (s *rekorV1Service) verifyLogEntrySignature(manifest *ReleaseManifest, body *RekorBody) error {
	if len(body.Spec.Signatures) == 0 {
		return fmt.Errorf("no signatures in Rekor body")
	}
	sig, err := base64.StdEncoding.DecodeString(body.Spec.Signatures[0].Signature)
	if err != nil {
		return fmt.Errorf("decode log entry signature: %w", err)
	}
	jcs, err := utils.JCSCanonicalize(manifest)
	if err != nil {
		return fmt.Errorf("JCS canonicalize: %w", err)
	}
	pae := utils.DSSEPAE(DSSEPayloadType, jcs)
	h := sha256.Sum256(pae)
	if !ecdsa.VerifyASN1(s.logEntryPubKey, h[:], sig) {
		return fmt.Errorf("log entry signature verification failed")
	}
	return nil
}

// verifyInclusionProof verifies the RFC 6962 Merkle inclusion proof for a Rekor entry
// using the audited implementation from github.com/transparency-dev/merkle.
func (s *rekorV1Service) verifyInclusionProof(entry *RekorEntry) error {
	proof := entry.Verification.InclusionProof

	if proof.TreeSize <= 0 {
		return fmt.Errorf("invalid tree size: %d", proof.TreeSize)
	}
	if proof.LogIndex < 0 || proof.LogIndex >= proof.TreeSize {
		return fmt.Errorf("log index %d out of range [0, %d)", proof.LogIndex, proof.TreeSize)
	}

	raw, err := base64.StdEncoding.DecodeString(entry.Body)
	if err != nil {
		return fmt.Errorf("decode entry body: %w", err)
	}
	leafHash := rfc6962.DefaultHasher.HashLeaf(raw)

	hashes := make([][]byte, len(proof.Hashes))
	for i, hx := range proof.Hashes {
		hashes[i], err = hex.DecodeString(hx)
		if err != nil {
			return fmt.Errorf("decode hash[%d]: %w", i, err)
		}
	}

	rootHash, err := hex.DecodeString(proof.RootHash)
	if err != nil {
		return fmt.Errorf("decode root hash: %w", err)
	}

	return merkleproof.VerifyInclusion(rfc6962.DefaultHasher,
		uint64(proof.LogIndex), uint64(proof.TreeSize), leafHash, hashes, rootHash)
}

// VerifyCheckpoint verifies the signed checkpoint in the inclusion proof.
func (s *rekorV1Service) verifyCheckpoint(proof *InclusionProof) error {
	checkpoint := proof.Checkpoint

	parts := strings.SplitN(checkpoint, "\n\u2014 ", 2)
	if len(parts) != 2 {
		return fmt.Errorf("invalid checkpoint format")
	}
	noteBody := parts[0]

	sigLine := strings.TrimSuffix(parts[1], "\n")
	sigParts := strings.SplitN(sigLine, " ", 2)
	if len(sigParts) != 2 {
		return fmt.Errorf("invalid checkpoint signature line")
	}
	sigBytes, err := base64.StdEncoding.DecodeString(sigParts[1])
	if err != nil {
		return fmt.Errorf("decode checkpoint signature: %w", err)
	}

	if len(sigBytes) < 5 {
		return fmt.Errorf("checkpoint signature too short")
	}
	rawSig := sigBytes[4:]

	noteHash := sha256.Sum256([]byte(noteBody))
	if !ecdsa.VerifyASN1(s.rekorV1PubKey, noteHash[:], rawSig) {
		return fmt.Errorf("checkpoint signature verification failed")
	}

	lines := strings.Split(strings.TrimRight(parts[0], "\n"), "\n")
	if len(lines) < 3 {
		return fmt.Errorf("checkpoint note body too short")
	}
	cpRoot, err := base64.StdEncoding.DecodeString(lines[2])
	if err != nil {
		return fmt.Errorf("decode checkpoint root hash: %w", err)
	}
	proofRoot, err := hex.DecodeString(proof.RootHash)
	if err != nil {
		return fmt.Errorf("decode proof root hash: %w", err)
	}
	if !bytes.Equal(cpRoot, proofRoot) {
		return fmt.Errorf("checkpoint root hash mismatch")
	}
	return nil
}

// VerifySET verifies the Signed Entry Timestamp (SET) for a Rekor entry.
func (s *rekorV1Service) verifySET(entry *RekorEntry) error {
	payload := struct {
		Body           string `json:"body"`
		IntegratedTime int64  `json:"integratedTime"`
		LogID          string `json:"logID"`
		LogIndex       int64  `json:"logIndex"`
	}{
		Body:           entry.Body,
		IntegratedTime: entry.IntegratedTime,
		LogID:          entry.LogID,
		LogIndex:       entry.LogIndex,
	}

	canonical, err := json.Marshal(payload)
	if err != nil {
		return fmt.Errorf("marshal SET payload: %w", err)
	}

	sig, err := base64.StdEncoding.DecodeString(entry.Verification.SignedEntryTimestamp)
	if err != nil {
		return fmt.Errorf("decode SET signature: %w", err)
	}

	h := sha256.Sum256(canonical)
	if !ecdsa.VerifyASN1(s.rekorV1PubKey, h[:], sig) {
		return fmt.Errorf("SET signature verification failed")
	}
	return nil
}

func parseECDSAPublicKeyPEM(pemData string) (*ecdsa.PublicKey, error) {
	block, _ := pem.Decode([]byte(pemData))
	if block == nil {
		return nil, fmt.Errorf("transparency: failed to decode public key PEM")
	}
	pub, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("transparency: failed to parse public key: %w", err)
	}
	key, ok := pub.(*ecdsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("transparency: public key is not ECDSA")
	}
	return key, nil
}
