"""X.509 Distinguished Name parsing for CAC/PIV integration.

Parses X.509 Distinguished Name (DN) strings into structured fields suitable
for building a PyAuthz Subject. Supports common DoD certificate DN formats.

This module handles string parsing only — no cryptography dependencies.
Certificate validation, OCSP, and CRL checking are infrastructure concerns
handled by the TLS termination layer (nginx, Apache, load balancer).

Example:
    from pyauthz.contrib.x509 import parse_dn, subject_from_dn

    dn = "CN=DOE.JANE.M.1234567890,OU=USN,OU=PKI,OU=DoD,O=U.S. Government,C=US"
    fields = parse_dn(dn)
    # {'CN': 'DOE.JANE.M.1234567890', 'OU': ['USN', 'PKI', 'DoD'],
    #  'O': 'U.S. Government', 'C': 'US'}

    subject = subject_from_dn(dn, roles=["analyst"])
    # Subject(id="1234567890", roles=["analyst"],
    #         attributes={"cn": "DOE.JANE.M.1234567890", "org": "U.S. Government", ...})
"""

from __future__ import annotations

import re

from pyauthz.models.subject import Subject


def parse_dn(dn: str) -> dict[str, str | list[str]]:
    """Parse an X.509 Distinguished Name string into a dict of fields.

    Handles both comma-separated and slash-separated DN formats.
    Multi-valued attributes (like multiple OUs) are returned as lists.

    Args:
        dn: The Distinguished Name string.

    Returns:
        Dict mapping attribute names to values. Multi-valued attributes
        (e.g., OU) are lists.

    Example:
        >>> parse_dn("CN=DOE.JANE.M.1234567890,OU=USN,OU=PKI,O=U.S. Government,C=US")
        {'CN': 'DOE.JANE.M.1234567890', 'OU': ['USN', 'PKI'], 'O': 'U.S. Government', 'C': 'US'}
    """
    # Normalize: strip leading slash if present, then split
    dn = dn.strip()
    if dn.startswith("/"):
        # Slash-separated format: /C=US/O=U.S. Government/OU=DoD/CN=...
        parts = [p for p in dn.split("/") if p]
    else:
        # Comma-separated format: CN=...,OU=...,O=...,C=...
        parts = [p.strip() for p in dn.split(",") if p.strip()]

    result: dict[str, str | list[str]] = {}
    for part in parts:
        if "=" not in part:
            continue
        key, _, value = part.partition("=")
        key = key.strip().upper()
        value = value.strip()

        if key in result:
            existing = result[key]
            if isinstance(existing, list):
                existing.append(value)
            else:
                result[key] = [existing, value]
        else:
            result[key] = value

    return result


def extract_dod_id(cn: str) -> str | None:
    """Extract a 10-digit DoD ID (EDIPI) from a Common Name.

    DoD CAC certificates typically use the format:
    LAST.FIRST.MIDDLE.1234567890

    Args:
        cn: The Common Name value from the certificate.

    Returns:
        The 10-digit DoD ID, or None if not found.

    Example:
        >>> extract_dod_id("DOE.JANE.M.1234567890")
        '1234567890'
    """
    match = re.search(r"\.(\d{10})$", cn)
    return match.group(1) if match else None


def extract_email(dn_fields: dict[str, str | list[str]]) -> str | None:
    """Extract an email address from DN fields.

    Checks the E, EMAIL, and EMAILADDRESS fields.

    Args:
        dn_fields: Parsed DN fields from parse_dn().

    Returns:
        The email address, or None if not found.
    """
    for key in ("E", "EMAIL", "EMAILADDRESS"):
        val = dn_fields.get(key)
        if val is not None:
            return val if isinstance(val, str) else val[0]
    return None


def subject_from_dn(
    dn: str,
    *,
    roles: list[str] | None = None,
    extra_attributes: dict | None = None,
) -> Subject:
    """Build a PyAuthz Subject from an X.509 Distinguished Name.

    Extracts the DoD ID (if present) as the subject ID, and populates
    attributes with CN, organization, OUs, and email.

    If no DoD ID is found in the CN, the full CN is used as the subject ID.

    Args:
        dn: The Distinguished Name string.
        roles: Roles to assign to the subject.
        extra_attributes: Additional attributes to merge into the subject.

    Returns:
        A Subject populated from the DN fields.

    Example:
        >>> subject_from_dn(
        ...     "CN=DOE.JANE.M.1234567890,OU=USN,OU=PKI,O=U.S. Government,C=US",
        ...     roles=["analyst"],
        ... )
        Subject(id='1234567890', roles=['analyst'], attributes={...})
    """
    fields = parse_dn(dn)

    cn = fields.get("CN", "")
    if isinstance(cn, list):
        cn = cn[0]

    dod_id = extract_dod_id(cn)
    subject_id = dod_id or cn or "unknown"

    org = fields.get("O", "")
    if isinstance(org, list):
        org = org[0]

    ou = fields.get("OU", [])
    if isinstance(ou, str):
        ou = [ou]

    country = fields.get("C", "")
    if isinstance(country, list):
        country = country[0]

    email = extract_email(fields)

    attributes: dict = {
        "cn": cn,
        "org": org,
        "ou": ou,
        "country": country,
        "dn": dn,
    }
    if dod_id:
        attributes["dod_id"] = dod_id
    if email:
        attributes["email"] = email
    if extra_attributes:
        attributes.update(extra_attributes)

    return Subject(
        id=subject_id,
        roles=roles or [],
        attributes=attributes,
    )
