import dataclasses
import enum
from dataclasses import dataclass, field
from datetime import datetime
from typing import List, Optional

from asn1crypto import x509
from pyhanko_certvalidator.context import (
    CertValidationPolicySpec,
    ValidationDataHandlers,
)
from pyhanko_certvalidator.context import (
    bootstrap_validation_data_handlers as _certvalidator_bootstrap_handlers,
)
from pyhanko_certvalidator.fetchers import FetcherBackend
from pyhanko_certvalidator.fetchers.requests_fetchers import (
    RequestsFetcherBackend,
)
from pyhanko_certvalidator.ltv.poe import POEManager
from pyhanko_certvalidator.ltv.types import ValidationTimingInfo
from pyhanko_certvalidator.revinfo.archival import CRLContainer, OCSPContainer

from pyhanko.sign.diff_analysis import DEFAULT_DIFF_POLICY, DiffPolicy
from pyhanko.sign.validation import KeyUsageConstraints

__all__ = [
    'SignatureValidationSpec',
    'PdfSignatureValidationSpec',
    'RevinfoOnlineFetchingRule',
    'LocalKnowledge',
    'RevocationInfoGatheringSpec',
    'KnownPOE',
    'bootstrap_validation_data_handlers'
]


class RevinfoOnlineFetchingRule(enum.Enum):
    ALWAYS_FETCH = enum.auto()
    """
    Always permit fetching revocation information from online sources.
    """

    NO_HISTORICAL_FETCH = enum.auto()
    """
    Only attempt to fetch revocation information when performing validation
    of a signature at the current time, and use the local cache in past
    validation evaluation.
    """
    # TODO document precisely what this means, esp. for the legacy API

    LOCAL_ONLY = enum.auto()
    """
    Only use locally cached revocation information.
    """


@dataclass(frozen=True)
class RevocationInfoGatheringSpec:
    online_fetching_rule: RevinfoOnlineFetchingRule = \
        RevinfoOnlineFetchingRule.NO_HISTORICAL_FETCH
    fetcher_backend: FetcherBackend = field(
        default_factory=RequestsFetcherBackend
    )


@dataclass(frozen=True)
class KnownPOE:
    digest: bytes
    poe_time: datetime


@dataclass(frozen=True)
class LocalKnowledge:
    known_ocsps: List[OCSPContainer] = field(default_factory=list)
    known_crls: List[CRLContainer] = field(default_factory=list)
    known_certs: List[x509.Certificate] = field(default_factory=list)
    known_poes: List[KnownPOE] = field(default_factory=list)

    def add_to_poe_manager(self, poe_manager: POEManager):
        for poe in self.known_poes:
            poe_manager.register_by_digest(poe.digest, dt=poe.poe_time)


@dataclass(frozen=True)
class SignatureValidationSpec:
    cert_validation_policy: CertValidationPolicySpec
    revinfo_gathering_policy: RevocationInfoGatheringSpec = \
        RevocationInfoGatheringSpec()
    ts_cert_validation_policy: Optional[CertValidationPolicySpec] = None
    ac_validation_policy: Optional[CertValidationPolicySpec] = None
    local_knowledge: LocalKnowledge = LocalKnowledge()
    key_usage_settings: KeyUsageConstraints = KeyUsageConstraints()


@dataclass(frozen=True)
class PdfSignatureValidationSpec:
    signature_validation_spec: SignatureValidationSpec
    diff_policy: Optional[DiffPolicy] = DEFAULT_DIFF_POLICY


def _backend_if_necessary(
        hist: bool, rule: RevinfoOnlineFetchingRule, backend: FetcherBackend) \
        -> Optional[FetcherBackend]:
    if rule == RevinfoOnlineFetchingRule.LOCAL_ONLY:
        return None
    elif rule == RevinfoOnlineFetchingRule.NO_HISTORICAL_FETCH and hist:
        return None
    else:
        return backend


def bootstrap_validation_data_handlers(
        spec: SignatureValidationSpec,
        timing_info: Optional[ValidationTimingInfo] = None,
        is_historical: Optional[bool] = None,
        poe_manager_override: Optional[POEManager] = None) \
        -> ValidationDataHandlers:
    if is_historical is None:
        hist = (
            timing_info.point_in_time_validation
            if timing_info is not None else False
        )
    else:
        hist = is_historical
    revinfo_policy = spec.revinfo_gathering_policy

    fetcher_backend = _backend_if_necessary(
        hist=hist, rule=revinfo_policy.online_fetching_rule,
        backend=revinfo_policy.fetcher_backend
    )

    knowledge = spec.local_knowledge
    handlers = _certvalidator_bootstrap_handlers(
        fetchers=fetcher_backend,
        crls=knowledge.known_crls,
        ocsps=knowledge.known_ocsps,
        certs=knowledge.known_certs,
        poe_manager=poe_manager_override
    )
    return handlers
