package reposign

import (
	"context"
	"fmt"
	"net/url"
	"os"
	"path/filepath"
	"strings"

	log "github.com/sirupsen/logrus"

	"github.com/netbirdio/netbird/client/internal/updater/downloader"
)

const (
	artifactPubKeysFileName    = "artifact-key-pub.pem"
	artifactPubKeysSigFileName = "artifact-key-pub.pem.sig"
	revocationFileName         = "revocation-list.json"
	revocationSignFileName     = "revocation-list.json.sig"

	keySizeLimit    = 5 * 1024 * 1024 //5MB
	signatureLimit  = 1024
	revocationLimit = 10 * 1024 * 1024
)

type ArtifactVerify struct {
	rootKeys    []PublicKey
	keysBaseURL *url.URL

	revocationList *RevocationList
}

func NewArtifactVerify(keysBaseURL string) (*ArtifactVerify, error) {
	allKeys, err := loadEmbeddedPublicKeys()
	if err != nil {
		return nil, err
	}

	return newArtifactVerify(keysBaseURL, allKeys)
}

func newArtifactVerify(keysBaseURL string, allKeys []PublicKey) (*ArtifactVerify, error) {
	ku, err := url.Parse(keysBaseURL)
	if err != nil {
		return nil, fmt.Errorf("invalid keys base URL %q: %v", keysBaseURL, err)
	}

	a := &ArtifactVerify{
		rootKeys:    allKeys,
		keysBaseURL: ku,
	}
	return a, nil
}

func (a *ArtifactVerify) Verify(ctx context.Context, version string, artifactFile string) error {
	version = strings.TrimPrefix(version, "v")

	revocationList, err := a.loadRevocationList(ctx)
	if err != nil {
		return fmt.Errorf("failed to load revocation list: %v", err)
	}
	a.revocationList = revocationList

	artifactPubKeys, err := a.loadArtifactKeys(ctx)
	if err != nil {
		return fmt.Errorf("failed to load artifact keys: %v", err)
	}

	signature, err := a.loadArtifactSignature(ctx, version, artifactFile)
	if err != nil {
		return fmt.Errorf("failed to download signature file for: %s, %v", filepath.Base(artifactFile), err)
	}

	artifactData, err := os.ReadFile(artifactFile)
	if err != nil {
		log.Errorf("failed to read artifact file: %v", err)
		return fmt.Errorf("failed to read artifact file: %w", err)
	}

	if err := ValidateArtifact(artifactPubKeys, artifactData, *signature); err != nil {
		return fmt.Errorf("failed to validate artifact: %v", err)
	}

	return nil
}

func (a *ArtifactVerify) loadRevocationList(ctx context.Context) (*RevocationList, error) {
	downloadURL := a.keysBaseURL.JoinPath("keys", revocationFileName).String()
	data, err := downloader.DownloadToMemory(ctx, downloadURL, revocationLimit)
	if err != nil {
		log.Debugf("failed to download revocation list '%s': %s", downloadURL, err)
		return nil, err
	}

	downloadURL = a.keysBaseURL.JoinPath("keys", revocationSignFileName).String()
	sigData, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
	if err != nil {
		log.Debugf("failed to download revocation list '%s': %s", downloadURL, err)
		return nil, err
	}

	signature, err := ParseSignature(sigData)
	if err != nil {
		log.Debugf("failed to parse revocation list signature: %s", err)
		return nil, err
	}

	return ValidateRevocationList(a.rootKeys, data, *signature)
}

func (a *ArtifactVerify) loadArtifactKeys(ctx context.Context) ([]PublicKey, error) {
	downloadURL := a.keysBaseURL.JoinPath("keys", artifactPubKeysFileName).String()
	log.Debugf("starting downloading artifact keys from: %s", downloadURL)
	data, err := downloader.DownloadToMemory(ctx, downloadURL, keySizeLimit)
	if err != nil {
		log.Debugf("failed to download artifact keys: %s", err)
		return nil, err
	}

	downloadURL = a.keysBaseURL.JoinPath("keys", artifactPubKeysSigFileName).String()
	log.Debugf("start downloading signature of artifact pub key from: %s", downloadURL)
	sigData, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
	if err != nil {
		log.Debugf("failed to download signature of public keys: %s", err)
		return nil, err
	}

	signature, err := ParseSignature(sigData)
	if err != nil {
		log.Debugf("failed to parse signature of public keys: %s", err)
		return nil, err
	}

	return ValidateArtifactKeys(a.rootKeys, data, *signature, a.revocationList)
}

func (a *ArtifactVerify) loadArtifactSignature(ctx context.Context, version string, artifactFile string) (*Signature, error) {
	artifactFile = filepath.Base(artifactFile)
	downloadURL := a.keysBaseURL.JoinPath("tag", "v"+version, artifactFile+".sig").String()
	data, err := downloader.DownloadToMemory(ctx, downloadURL, signatureLimit)
	if err != nil {
		log.Debugf("failed to download artifact signature: %s", err)
		return nil, err
	}

	signature, err := ParseSignature(data)
	if err != nil {
		log.Debugf("failed to parse artifact signature: %s", err)
		return nil, err
	}

	return signature, nil

}

func loadEmbeddedPublicKeys() ([]PublicKey, error) {
	files, err := embeddedCerts.ReadDir(embeddedCertsDir)
	if err != nil {
		return nil, fmt.Errorf("failed to read embedded certs: %w", err)
	}

	var allKeys []PublicKey
	for _, file := range files {
		if file.IsDir() {
			continue
		}

		data, err := embeddedCerts.ReadFile(embeddedCertsDir + "/" + file.Name())
		if err != nil {
			return nil, fmt.Errorf("failed to read cert file %s: %w", file.Name(), err)
		}

		keys, err := parsePublicKeyBundle(data, tagRootPublic)
		if err != nil {
			return nil, fmt.Errorf("failed to parse cert %s: %w", file.Name(), err)
		}

		allKeys = append(allKeys, keys...)
	}

	if len(allKeys) == 0 {
		return nil, fmt.Errorf("no valid public keys found in embedded certs")
	}

	return allKeys, nil
}
