package verifier

import (
	"crypto/x509"
	"encoding/base64"
	"encoding/hex"
	"encoding/json"
	"encoding/pem"
	"strconv"
	"strings"
	"time"

	ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
	"github.com/pkg/errors"
	v1common "github.com/sigstore/protobuf-specs/gen/pb-go/common/v1"
	v1 "github.com/sigstore/protobuf-specs/gen/pb-go/rekor/v1"
	"github.com/sigstore/sigstore-go/pkg/bundle"
	"github.com/sigstore/sigstore-go/pkg/root"
	"github.com/sigstore/sigstore-go/pkg/tlog"
	"github.com/sigstore/sigstore-go/pkg/verify"
)

const (
	annotationCert = "dev.sigstore.cosign/certificate"
	// annotationChain     = "dev.sigstore.cosign/chain"
	annotationSignature = "dev.cosignproject.cosign/signature"
	annotationBundle    = "dev.sigstore.cosign/bundle"
)

// hashedRecordSignedEntity implements verify.SignedEntity using cosign oldbundle format.
type hashedRecordSignedEntity struct {
	mfst  *ocispecs.Manifest
	cert  verify.VerificationContent
	sig   *messageSignatureContent
	isDHI bool
}

var _ verify.SignedEntity = &hashedRecordSignedEntity{}
var _ verify.SignatureContent = &hashedRecordSignedEntity{}
var _ verify.VerificationContent = &hashedRecordSignedEntity{}

func newHashedRecordSignedEntity(mfst *ocispecs.Manifest, isDHI bool) (verify.SignedEntity, error) {
	if len(mfst.Layers) == 0 {
		return nil, errors.New("no layers in manifest")
	}
	desc := mfst.Layers[0]
	sigStr, ok := desc.Annotations[annotationSignature]
	if !ok {
		return nil, errors.New("no signature annotation found")
	}
	sig, err := base64.StdEncoding.DecodeString(sigStr)
	if err != nil {
		return nil, errors.Wrapf(err, "decode signature")
	}
	dgstBytest, err := hex.DecodeString(desc.Digest.Hex())
	if err != nil {
		return nil, errors.Wrapf(err, "decode digest")
	}

	hr := &hashedRecordSignedEntity{
		mfst: mfst,
		sig: &messageSignatureContent{
			digest:          dgstBytest,
			signature:       sig,
			digestAlgorithm: desc.Digest.Algorithm().String(),
		},
		isDHI: isDHI,
	}

	if !isDHI {
		certData := desc.Annotations[annotationCert]
		if certData == "" {
			return nil, errors.Errorf("no certificate annotation found")
		}
		block, _ := pem.Decode([]byte(certData))
		if block == nil {
			return nil, errors.New("no PEM certificate found in annotation")
		}
		cert, err := x509.ParseCertificate(block.Bytes)
		if err != nil {
			return nil, errors.WithStack(err)
		}
		hr.cert = bundle.NewCertificate(cert)
	}

	return hr, nil
}

func (d *hashedRecordSignedEntity) HasInclusionPromise() bool {
	return true
}

func (d *hashedRecordSignedEntity) HasInclusionProof() bool {
	return true
}

func (d *hashedRecordSignedEntity) SignatureContent() (verify.SignatureContent, error) {
	return d, nil
}

func (d *hashedRecordSignedEntity) Timestamps() ([][]byte, error) {
	return nil, nil
}

func (d *hashedRecordSignedEntity) TlogEntries() ([]*tlog.Entry, error) {
	bundleBytes, ok := d.extractBundle()
	if !ok {
		return nil, nil
	}
	bundle, err := parseRekorBundle(bundleBytes)
	if err != nil {
		return nil, errors.Wrap(err, "parse rekor bundle")
	}
	logIDRaw, err := hex.DecodeString(bundle.LogID)
	if err != nil {
		return nil, errors.Wrap(err, "decode logID")
	}

	tl, err := tlog.NewTlogEntry(&v1.TransparencyLogEntry{
		LogIndex:          bundle.LogIndex,
		LogId:             &v1common.LogId{KeyId: logIDRaw},
		IntegratedTime:    bundle.IntegratedTime,
		CanonicalizedBody: bundle.Body,
		KindVersion: &v1.KindVersion{
			Kind:    "hashedrekord",
			Version: "0.0.1",
		},
		InclusionPromise: &v1.InclusionPromise{
			SignedEntryTimestamp: bundle.Signature,
		},
	})
	if err != nil {
		return nil, errors.Wrap(err, "create tlog entry")
	}
	return []*tlog.Entry{tl}, nil
}

func (d *hashedRecordSignedEntity) VerificationContent() (verify.VerificationContent, error) {
	return d, nil
}

func (d *hashedRecordSignedEntity) Version() (string, error) {
	return "v0.1", nil
}

func (d *hashedRecordSignedEntity) Signature() []byte {
	return d.sig.signature
}

func (d *hashedRecordSignedEntity) EnvelopeContent() verify.EnvelopeContent {
	return nil
}

func (d *hashedRecordSignedEntity) MessageSignatureContent() verify.MessageSignatureContent {
	return d.sig
}

type messageSignatureContent struct {
	digest          []byte
	digestAlgorithm string
	signature       []byte
}

func (m *messageSignatureContent) Digest() []byte {
	return m.digest
}

func (m *messageSignatureContent) DigestAlgorithm() string {
	return m.digestAlgorithm
}

func (m *messageSignatureContent) Signature() []byte {
	return m.signature
}

// CompareKey traces parameters and returns false.
func (d *hashedRecordSignedEntity) CompareKey(k any, tm root.TrustedMaterial) bool {
	if d.isDHI {
		return (&bundle.PublicKey{}).CompareKey(k, tm)
	}
	if _, ok := k.(*x509.Certificate); !ok {
		return false
	}
	return d.cert.CompareKey(k, tm)
}

func (d *hashedRecordSignedEntity) ValidAtTime(t time.Time, tm root.TrustedMaterial) bool {
	if d.isDHI {
		return (&bundle.PublicKey{}).ValidAtTime(t, tm)
	}
	return d.cert.ValidAtTime(t, tm)
}

func (d *hashedRecordSignedEntity) Certificate() *x509.Certificate {
	if d.isDHI {
		return nil
	}
	return d.cert.Certificate()
}

func (d *hashedRecordSignedEntity) Intermediates() []*x509.Certificate {
	if d.isDHI {
		return nil
	}
	return d.cert.Intermediates()
}

func (d *hashedRecordSignedEntity) PublicKey() verify.PublicKeyProvider {
	if d.isDHI {
		return bundle.PublicKey{}
	}
	return d.cert.PublicKey()
}

func (d *hashedRecordSignedEntity) extractBundle() ([]byte, bool) {
	if len(d.mfst.Layers) == 0 {
		return nil, false
	}
	desc := d.mfst.Layers[0]
	bundleStr := desc.Annotations[annotationBundle]
	if bundleStr == "" {
		return nil, false
	}
	return []byte(bundleStr), true
}

type rekorBundle struct {
	Body           []byte
	Signature      []byte
	LogID          string
	IntegratedTime int64
	LogIndex       int64
}

func parseRekorBundle(bundleBytes []byte) (*rekorBundle, error) {
	var nb struct {
		Content struct {
			VerificationMaterial struct {
				TlogEntries []struct {
					LogIndex any `json:"logIndex"`
					LogID    struct {
						KeyID string `json:"keyId"`
					} `json:"logId"`
					IntegratedTime   any `json:"integratedTime"`
					InclusionPromise struct {
						SignedEntryTimestamp []byte `json:"signedEntryTimestamp"`
					} `json:"inclusionPromise"`
					CanonicalizedBody []byte `json:"canonicalizedBody"`
				} `json:"tlogEntries"`
			} `json:"verificationMaterial"`
		} `json:"content"`
	}
	if json.Unmarshal(bundleBytes, &nb) == nil && len(nb.Content.VerificationMaterial.TlogEntries) > 0 {
		e := nb.Content.VerificationMaterial.TlogEntries[0]
		if len(e.CanonicalizedBody) != 0 && len(e.InclusionPromise.SignedEntryTimestamp) != 0 {
			b := &rekorBundle{
				Body:      e.CanonicalizedBody,
				Signature: e.InclusionPromise.SignedEntryTimestamp,
				LogID:     strings.ToLower(e.LogID.KeyID),
			}

			it, err1 := anyToInt64(e.IntegratedTime)
			if err1 == nil {
				b.IntegratedTime = it
			}
			li, err2 := anyToInt64(e.LogIndex)
			if err2 == nil {
				b.LogIndex = li
			}
			return b, nil
		}
	}

	// Fallback to older cosign bundle shape
	var bundle struct {
		SignedEntryTimestamp []byte `json:"SignedEntryTimestamp"`
		Payload              struct {
			Body           []byte `json:"body"`
			LogID          any    `json:"logID"`
			IntegratedTime any    `json:"integratedTime"`
			LogIndex       any    `json:"logIndex"`
		} `json:"Payload"`
		LogID          any `json:"logID"`
		IntegratedTime any `json:"integratedTime"`
		LogIndex       any `json:"logIndex"`
	}
	if err := json.Unmarshal(bundleBytes, &bundle); err != nil {
		return nil, errors.Wrap(err, "parse bundle json")
	}

	b := &rekorBundle{
		Body:      bundle.Payload.Body,
		Signature: bundle.SignedEntryTimestamp,
	}

	// Prefer top-level fields when present; otherwise fall back to nested under Payload
	// Handle string/number types
	if s, ok := bundle.LogID.(string); ok {
		b.LogID = s
	}
	if b.LogID == "" {
		if s, ok := bundle.Payload.LogID.(string); ok {
			b.LogID = s
		}
	}
	if v, err := anyToInt64(bundle.IntegratedTime); err == nil {
		b.IntegratedTime = v
	}
	if b.IntegratedTime == 0 {
		if v, err := anyToInt64(bundle.Payload.IntegratedTime); err == nil {
			b.IntegratedTime = v
		}
	}
	if v, err := anyToInt64(bundle.LogIndex); err == nil {
		b.LogIndex = v
	}
	if b.LogIndex == 0 {
		if v, err := anyToInt64(bundle.Payload.LogIndex); err == nil {
			b.LogIndex = v
		}
	}
	return b, nil
}

func anyToInt64(v any) (int64, error) {
	switch t := v.(type) {
	case nil:
		return 0, errors.New("nil")
	case float64:
		return int64(t), nil
	case json.Number:
		return t.Int64()
	case string:
		if t == "" {
			return 0, errors.New("empty")
		}
		return strconv.ParseInt(t, 10, 64)
	case int64:
		return t, nil
	case int:
		return int64(t), nil
	default:
		return 0, errors.Errorf("unsupported type %T", v)
	}
}
