from __future__ import annotations


FIELD_ALIASES: dict[str, tuple[str, ...]] = {
    "srcip": ("srcip", "src_ip", "source_ip", "source.ip", "source.address", "src", "src_addr", "srcaddr", "sourceaddress", "client_ip", "client.ip", "client.address", "clientip", "client", "remote_addr", "remote_ip", "ip", "ipaddress", "related.ip", "gl2_remote_ip", "winlog_event_data_ipaddress", "winlog.event_data.ipaddress", "winlog.event_data.ip_address", "event_data_ipaddress"),
    "dstip": ("dstip", "dst_ip", "destination_ip", "destination.ip", "destination.address", "dst", "dst_addr", "dstaddr", "destinationaddress", "dest_ip", "dest", "server_ip", "server.ip", "server.address", "server", "upstream"),
    "srcport": ("srcport", "src_port", "source_port", "source.port", "sport", "client_port", "client.port", "clientport"),
    "dstport": ("dstport", "dst_port", "destination_port", "destination.port", "dest_port", "dport", "server_port", "server.port", "serverport"),
    "eventtime": ("eventtime", "timestamp", "@timestamp", "time", "event_time", "eventtime_ms", "created_at", "event.created", "event.ingested", "winlog.time_created"),
    "severity": ("severity", "level", "priority", "sev", "loglevel", "log_level", "log.level", "event_severity", "event.severity", "winlog.level", "winlog.level_name"),
    "action": ("action", "act", "fw_action", "rule_action", "policy_action", "event_action", "event.action", "disposition", "outcome", "result", "status", "event_outcome", "event.outcome"),
    "type": ("type", "event_type", "log_type", "category", "event_category", "event.category", "event.kind", "event.module", "event.dataset", "facility"),
    "subtype": ("subtype", "sub_type", "event_subtype", "subcategory", "event.type"),
    "service": ("service", "service_name", "service.name", "dst_service", "app", "appname", "application", "application_name", "proto", "protocol", "transport", "network.transport", "network.protocol", "query_type", "qt"),
    "username": ("username", "targetusername", "target_user_name", "winlog_event_data_targetusername", "winlog.event_data.targetusername", "winlog.event_data.target_user_name", "subjectusername", "subject_user_name", "winlog_event_data_subjectusername", "winlog.event_data.subjectusername", "winlog.event_data.subject_user_name", "user", "user.name", "user.id", "user_name", "account", "account_name", "xauthuser", "actor", "actor_id", "principal", "principal_name", "login", "login_name"),
    "hostname": ("hostname", "host", "host.name", "computer", "computer_name", "workstation", "workstation_name", "device_name", "agent_name", "agent.hostname", "agent.name", "observer.hostname", "winlog_computer_name", "winlog.computer_name"),
    "eventid": ("eventid", "event_id", "winlog_event_id", "winlog.event_id", "event_code", "event.code", "windows_event_id"),
    "dns_query": ("dns_query", "query_domain", "qh", "dns_question", "question", "queried_domain"),
    "context": ("query_domain", "qh", "dns_query", "url", "url.full", "url.path", "uri", "request", "request_uri", "path", "domain", "hostname", "message", "msg", "full_message", "event_message", "event_original", "event.original", "process.command_line", "powershell.command.value"),
}

DEFAULT_SEARCH_FIELDS = tuple(dict.fromkeys(field for aliases in FIELD_ALIASES.values() for field in aliases))


def first_field(fields: dict[str, str], *names: str) -> str:
    for name in names:
        value = str(fields.get(name.lower(), "")).strip()
        if value and value not in {"-", "unknown", "n/a", "none", "null"}:
            return value
    return ""


def canonical_value(fields: dict[str, str], canonical: str) -> str:
    return first_field(fields, *FIELD_ALIASES.get(canonical, (canonical,)))


def normalize_fields(fields: dict[str, str], mapping: dict[str, object] | None = None) -> dict[str, str]:
    normalized = dict(fields)
    mapping = mapping or {}
    for canonical, aliases in FIELD_ALIASES.items():
        mapped = mapping.get(canonical)
        candidates = (str(mapped).lower(),) if isinstance(mapped, str) and mapped else aliases
        value = first_field(normalized, *candidates)
        if value:
            normalized[canonical] = value
    return normalized
