from wordfence.util.pcre import PCRE_DEFAULT_MATCH_LIMIT, \
        PCRE_DEFAULT_MATCH_LIMIT_RECURSION
from wordfence.util.units import byte_length

from ...scanning.matching import MatchEngine
from ..subcommands import SubcommandDefinition, UsageExample
from ..config.typing import ConfigDefinitions
from .reporting import SCAN_REPORT_CONFIG_OPTIONS


config_definitions: ConfigDefinitions = {
    "read-stdin": {
        "description": "Read paths from stdin. If not specified, paths will "
                       "automatically be read from stdin when input is not "
                       "from a TTY.",
        "context": "ALL",
        "argument_type": "OPTIONAL_FLAG",
        "default": None
    },
    "file-list-separator": {
        "short_name": "s",
        "description": "Separator used when listing files via stdin. Defaults "
                       "to the null byte.",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": "AA==",
        "default_type": "base64"
    },
    **SCAN_REPORT_CONFIG_OPTIONS,
    "exclude-signatures": {
        "short_name": "e",
        "description": "Specify rule IDs to exclude from the scan. Can be "
                       "comma-delimited and/or specified multiple times.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ",",
            "value_type": int
        }
    },
    "include-signatures": {
        "short_name": "i",
        "description": "Specify rule IDs to include in the scan. Can be "
                       "comma-delimited and/or specified multiple times.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ",",
            "value_type": int
        }
    },
    "images": {
        "description": "Include image files in the scan.",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": False
    },
    "include-all-files": {
        "short_name": "a",
        "description": "Scan all files. By default, only files matching "
                       "certain extensions are scanned",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": False
    },
    "include-files": {
        "short_name": "n",
        "description": "Only scan filenames that are exact matches. Can be "
                       "used multiple times.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ",",
            "accepts_file": True
        }
    },
    "exclude-files": {
        "short_name": "x",
        "description": "Do not scan filenames that are exact matches. Can be "
                       "used multiple times. Denials take precedence over "
                       "allows.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ",",
            "accepts_file": True
        }
    },
    "include-files-pattern": {
        "short_name": "N",
        "description": "Python regex allow pattern. Only matching filenames "
                       "will be scanned.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ","
        }
    },
    "exclude-files-pattern": {
        "short_name": "X",
        "description": "Python regex deny pattern. Matching filenames will "
                       "not be scanned.",
        "context": "ALL",
        "argument_type": "OPTION_REPEATABLE",
        "default": None,
        "meta": {
            "separator": ","
        }
    },
    "allow-io-errors": {
        "description": "Allow scanning to continue if IO errors are "
                       "encountered. Files that cannot be read will "
                       "be skipped and a warning will be logged. This is "
                       "the default behavior.",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": True
    },
    "chunk-size": {
        "short_name": "z",
        "description": "Size of file chunks that will be scanned. Use a whole "
                       "number followed by one of the following suffixes: b "
                       "(byte), k (kibibyte), m (mebibyte). Defaults to 3m.",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": 3145728,
        "meta": {
            "value_type": byte_length
        }
    },
    "scanned-content-limit": {
        "short_name": "M",
        "description": "The maximum amount of data to scan in each file."
                       " Content beyond this limit will not be scanned."
                       " Defaults to 50 mebibytes. Use a whole number followed"
                       " by one of the following suffixes: b (byte),"
                       " k (kibibyte), m (mebibyte).",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": byte_length('50m'),
        "meta": {
            "value_type": byte_length
        }
    },
    "match-engine": {
        "description": "The regex engine to use for malware scanning.",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": MatchEngine.get_default_option(),
        "meta": {
            "valid_options": MatchEngine.get_options()
        }
    },
    "match-all": {
        "description": "If set, all possible signatures will be checked "
                       "against each scanned file. Otherwise, only the "
                       "first matching signature will be reported",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": False
    },
    "pcre-backtrack-limit": {
        "description": "The regex backtracking limit for signature evaluation",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": PCRE_DEFAULT_MATCH_LIMIT,
        "meta": {
            "value_type": int
        }
    },
    "pcre-recursion-limit": {
        "description": "The regex recursion limit for signature evaluation",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": PCRE_DEFAULT_MATCH_LIMIT_RECURSION,
        "meta": {
            "value_type": int
        }
    },
    "workers": {
        "short_name": "w",
        "description": "Number of worker processes used to perform scanning. "
                       "Defaults to 1 worker process.",
        "context": "ALL",
        "argument_type": "OPTION",
        "default": 1
    },
    "progress": {
        "description": "Display scan progress in the terminal with a curses "
                       "interface",
        "context": "CLI",
        "argument_type": "FLAG",
        "default": False
    },
    "pre-compile": {
        "description": "Pre-compile and cache the signature set without "
                       "actually running a scan",
        "context": "CLI",
        "argument_type": "FLAG",
        "default": False,
        "category": "Signature Compilation"
    },
    "pre-compile-generic": {
        "description": "Pre-compile and cache the signature set without "
                       "any CPU-specific optimizations and without running "
                       "a scan",
        "context": "CLI",
        "argument_type": "FLAG",
        "default": False,
        "category": "Signature Compilation"
    },
    "pattern-database-path": {
        "description": "Use an alternate path for storage of the pattern "
                       "database",
        "context": "ALL",
        "argument_type": "OPTION",
        "meta": {
            "accepts_file": True
        },
        "default": None,
        "category": "Signature Compilation"
    },
    "compile-local": {
        "description": "Always compile the signature set locally rather than "
                       "attempting to download a pre-compiled set",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": False,
        "category": "Signature Compilation"
    },
    "re-compile": {
        "description": "Always re-compile the signature set rather than using "
                       "a cached version (when compiling locally)",
        "context": "CLI",
        "argument_type": "FLAG",
        "default": False,
        "category": "Signature Compilation"
    },
    "profile": {
        "description": "Profile scan performance",
        "context": "CLI",
        "argument_type": "FLAG",
        "default": False,
        "hidden": True
    },
    "profile-path": {
        "description": "Path at which to save profiling results",
        "context": "CLI",
        "argument_type": "OPTION",
        "default": None,
        "hidden": True
    },
    "direct-io": {
        "short_name": "D",
        "description": "Use direct IO when opening files to avoid caching",
        "context": "ALL",
        "argument_type": "FLAG",
        "default": False
    }
}


cacheable_types = {
    'wordfence.intel.signatures.SignatureSet',
    'wordfence.intel.signatures.CommonString',
    'wordfence.intel.signatures.Signature',
    'wordfence.intel.signatures.PrecompiledSignatureSet',
    'wordfence.api.licensing.License'
}

examples = [
    UsageExample(
        'Scan /var/www/html for malicious files and display real-time output',
        'wordfence malware-scan --progress /var/www/html'
    ),
    UsageExample(
        'Scan for malicious files in multiple directories and record the '
        'results to a CSV file',
        'wordfence malware-scan --output-path /home/user/wordfence-cli.csv '
        '/home/user-one/dir /home/user-two/dir /home/user-three/dir'
    ),
    UsageExample(
        'Scan a directory for malicious files with any extension using 4 '
        'worker processes, writing each matching file name to a line in '
        'results.txt',
        'wordfence malware-scan -a -w 4 --output-path /path/to/results.txt '
        '--output-format line-delimited /path/to/scan'
    ),
    UsageExample(
        'Scan only files with a .js extension for malware',
        'wordfence malware-scan --include-files-pattern ".*\\.js$" '
        '/path/to/scan'
    ),
    UsageExample(
        'Find files updated in the last 60 and scan them for malware',
        'find /var/www/ -cmin -60 -type f -print0 | wordfence malware-scan '
        '--output-path /home/username/wordfence-cli.csv'
    )
]

definition = SubcommandDefinition(
    name='malware-scan',
    usage='[OPTIONS] [PATH]...',
    description='Scan files for malware',
    config_definitions=config_definitions,
    config_section='MALWARE_SCAN',
    cacheable_types=cacheable_types,
    previous_names={'scan'},
    examples=examples,
    uses_license=True,
    accepts_directories=True,
    accepts_files=True
)
