import ast
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Tuple

from panther_analysis_tool.schemas import (
    GLOBAL_SCHEMA,
    POLICY_SCHEMA,
    RULE_SCHEMA,
    extract_keys_schema,
)

EXPERIMENTAL_STATUS = "experimental"
DEPRECATED_STATUS = "deprecated"


@dataclass
class Filter:
    key: str
    values: List[Any] | Any
    inverted: bool = False


# Parses the filters, expects a list of strings
def parse_filter_args(str_filters: Optional[List[str]]) -> Tuple[List[Filter], List[Filter]]:
    parsed_filters: Dict[str, Any] = {}
    parsed_filters_inverted: Dict[str, Any] = {}
    if str_filters is None:
        return [], []
    for filt in str_filters:
        split = filt.split("=")
        if len(split) != 2 or split[0] == "" or split[1] == "":
            raise ValueError(f"Filter {filt} is not in format KEY=VALUE")
        # Check for "!="
        invert_filter = split[0].endswith("!")
        if invert_filter:
            split[0] = split[0][:-1]  # Remove the trailing "!"
        key = split[0]
        valid_keys = (
            extract_keys_schema(GLOBAL_SCHEMA)
            | extract_keys_schema(POLICY_SCHEMA)
            | extract_keys_schema(RULE_SCHEMA)
        )
        if key not in valid_keys:
            raise ValueError(f"Filter key {key} is not a valid filter field")
        if invert_filter:
            parsed_filters_inverted[key] = split[1].split(",")
        else:
            parsed_filters[key] = split[1].split(",")
        # Handle boolean fields
        if key == "Enabled":
            try:
                bool_value = bool(strtobool(split[1]))
            except ValueError:
                raise ValueError(f"Filter key {key} should have either true or false")
            if invert_filter:
                parsed_filters_inverted[key] = [bool_value]
            else:
                parsed_filters[key] = [bool_value]

    filters = [Filter(key=key, values=values) for key, values in parsed_filters.items()]
    filters_inverted = [
        Filter(key=key, values=values, inverted=True)
        for key, values in parsed_filters_inverted.items()
    ]
    return filters, filters_inverted


def get_filters_with_status_filters(
    str_filters: Optional[List[str]],
) -> Tuple[List[Filter], List[Filter]]:
    filters, filters_inverted = parse_filter_args(str_filters)
    filters, filters_inverted = add_status_filters(filters, filters_inverted)
    return filters, filters_inverted


def add_status_filters(
    filters: List[Filter], filters_inverted: List[Filter]
) -> Tuple[List[Filter], List[Filter]]:
    blocked_statuses = [EXPERIMENTAL_STATUS, DEPRECATED_STATUS]

    # strip experimental/deprecated from any positive Status filter the user provided
    for i, filt in enumerate(filters):
        if filt.key == "Status":
            existing_values = filt.values if isinstance(filt.values, list) else [filt.values]
            cleaned = [v for v in existing_values if v.lower() not in blocked_statuses]
            filters[i] = Filter(key="Status", values=cleaned)

    # ensure the inverted filter always includes experimental and deprecated
    for i, filt in enumerate(filters_inverted):
        if filt.key == "Status":
            existing_values = filt.values if isinstance(filt.values, list) else [filt.values]
            merged_values = list(existing_values)
            for val in blocked_statuses:
                if val not in merged_values:
                    merged_values.append(val)
            filters_inverted[i] = Filter(key="Status", values=merged_values, inverted=True)
            return filters, filters_inverted

    filters_inverted.append(Filter(key="Status", values=list(blocked_statuses), inverted=True))
    return filters, filters_inverted


def strtobool(val: str) -> int:
    val = val.lower()
    if val in ("y", "yes", "t", "true", "on", "1"):
        return 1
    elif val in ("n", "no", "f", "false", "off", "0"):
        return 0
    else:
        raise ValueError("invalid truth value %r" % (val,))


def collect_top_level_imports(py: bytes) -> set[str]:
    """
    Collects all imports from a Python file by parsing the file is an AST
    and extracting the top level imports.

    Example:
    ```python
        from top import foo
        import bar
        import baz.qux
        from baz.qux import quux
        from scoob.qux import quux as quuux
    ```

    Outputs:
    ```python
        {"top", "bar", "baz", "scoob"}
    ```

    Args:
        py: The Python file to parse.

    Returns:
        A set of top level imports.
    """
    try:
        tree = ast.parse(py.decode("utf-8"))
    except (UnicodeDecodeError, SyntaxError):
        return set()

    imports: set[str] = set()
    for node in ast.walk(tree):
        if isinstance(node, ast.Import):
            imports.update(alias.name.split(".")[0] for alias in node.names)
        elif isinstance(node, ast.ImportFrom) and node.module:
            imports.add(node.module.split(".")[0])

    return imports


def search_terms_to_filters(search_terms: list[str]) -> list[Filter]:
    result: list[Filter] = []

    for search_term in search_terms:
        if search_term.strip() == "":
            continue

        try:
            filters, filters_inverted = parse_filter_args([search_term])
            result.extend(filters)
            result.extend(filters_inverted)
        except ValueError:
            result.append(Filter(key="", values=[search_term]))

    return result
