// Package driver exposes interfaces and types certgraph drivers must implement
package driver

import (
	"context"
	"crypto/x509"
	"sort"
	"strings"

	"github.com/lanrat/certgraph/fingerprint"
	"github.com/lanrat/certgraph/status"
)

// Drivers contains all the drivers that have been registered
var Drivers []string

// AddDriver registers a driver name in the global driver registry.
// This should be called in the init() function of every driver implementation
// to make it available for use.
func AddDriver(name string) {
	Drivers = append(Drivers, name)
}

// Driver is a universal unifying interface to support CT, http and much more!
type Driver interface {
	// QueryDomain is the main entrypoint for Driver Searching
	// The domain provided will return a CertDriver instance which can be used to query the
	// certificates for the provided domain using the driver
	QueryDomain(ctx context.Context, domain string) (Result, error)

	// GetName returns the name of the driver
	GetName() string
}

// Result is a sub-driver that allows querying certificate details from a previously queried domain
type Result interface {
	// GetStatus returns the status of the initial domain queried with the Driver.QueryDomain call
	GetStatus() status.Map

	// returns a list of additional related domains discovered while looking up the provided domain
	GetRelated() ([]string, error)

	// GetFingerprints returns an array of the certificate fingerprints associated with the Domain
	// pass return fingerprints to QueryCert to get certificate details
	GetFingerprints() (FingerprintMap, error)

	// QueryCert returns the details of the provided certificate or an error if not found
	QueryCert(ctx context.Context, fp fingerprint.Fingerprint) (*CertResult, error)
}

// FingerprintMap stores a mapping of domains to Fingerprints returned from the driver
// in the case where multiple domains where queries (redirects, related, etc..) the
// matching certificates will be in this map
// the fingerprints returned are guaranteed to be a complete result for the domain's certs, but related domains may or may not be complete
type FingerprintMap map[string][]fingerprint.Fingerprint

// Add appends a certificate fingerprint to the list associated with a domain.
// If the domain doesn't exist in the map, it creates a new entry.
func (f FingerprintMap) Add(domain string, fp fingerprint.Fingerprint) {
	f[domain] = append(f[domain], fp)
}

// CertResult is an object to hold the fingerprint and Domains for a returned certificate
type CertResult struct {
	Fingerprint fingerprint.Fingerprint
	Domains     []string
}

// NewCertResult creates a new CertResult struct from an x509 cert
func NewCertResult(cert *x509.Certificate) *CertResult {
	certResult := new(CertResult)

	// generate Fingerprint
	certResult.Fingerprint = fingerprint.FromRawCertBytes(cert.Raw)

	// domains
	// used to ensure uniq entries in domains array
	domainMap := make(map[string]bool)
	// add the CommonName just to be safe
	cn := strings.ToLower(cert.Subject.CommonName)
	if len(cn) > 0 {
		domainMap[cn] = true
	}
	for _, domain := range cert.DNSNames {
		if len(domain) > 0 {
			domain = strings.ToLower(domain)
			domainMap[domain] = true
		}
	}
	for domain := range domainMap {
		certResult.Domains = append(certResult.Domains, domain)
	}
	sort.Strings(certResult.Domains)

	return certResult
}
