from .schema import Schema
from typing import Dict, Any

def compare_schemas(old: Schema, new: Schema) -> Dict[str, Any]:
    """
    Compare two Schema objects and return a dict describing:
      - added_columns
      - removed_columns
      - changed_columns: {col: {old: <props>, new: <props>, breaking: bool}}
      - breaking: overall bool (True if any breaking change)
    Rules for breaking changes:
      - removed column -> breaking
      - change of dtype -> breaking
      - change nullable True->False -> breaking
    Adding a new column (nullable) is non-breaking.
    """
    old_cols = set(old.columns.keys())
    new_cols = set(new.columns.keys())

    added = list(new_cols - old_cols)
    removed = list(old_cols - new_cols)
    changed = {}

    breaking = False

    for col in (old_cols & new_cols):
        o = old.columns[col]
        n = new.columns[col]
        col_changes = {}
        if o.dtype != n.dtype:
            col_changes["dtype"] = {"old": getattr(o.dtype, "__name__", str(o.dtype)),
                                    "new": getattr(n.dtype, "__name__", str(n.dtype))}
            col_changes["breaking"] = True
            breaking = True
        if o.not_null and not n.not_null:
            # making required -> optional is compatible
            col_changes.setdefault("nullable", {})["old"] = o.not_null
            col_changes["nullable"]["new"] = n.not_null
            col_changes["nullable"]["breaking"] = False
        if (not o.not_null) and n.not_null:
            # optional -> required = breaking
            col_changes.setdefault("nullable", {})["old"] = o.not_null
            col_changes["nullable"]["new"] = n.not_null
            col_changes["nullable"]["breaking"] = True
            breaking = True
        if o.unique != n.unique:
            col_changes["unique"] = {"old": o.unique, "new": n.unique, "breaking": False}
            # making unique may be breaking implicitly but flagging as non-breaking for now
        if o.min_value != n.min_value or o.max_value != n.max_value:
            col_changes["bounds"] = {"old_min": o.min_value, "new_min": n.min_value,
                                     "old_max": o.max_value, "new_max": n.max_value,
                                     "breaking": False}

        if col_changes:
            changed[col] = col_changes

    # Removing column is breaking
    if removed:
        breaking = True

    return {
        "added_columns": added,
        "removed_columns": removed,
        "changed_columns": changed,
        "breaking": breaking
    }
