#!/usr/bin/env python3
"""
Terminology migration script: Merchant/Store -> Merchant/Store.

Performs bulk find-and-replace across the entire codebase.
Replacements are ordered longest-first to avoid partial match issues.

Usage:
    python scripts/rename_terminology.py [--dry-run]
"""

import os
import sys

# Root of the project
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))

# Directories/files to skip
SKIP_DIRS = {
    ".git",
    "__pycache__",
    "venv",
    ".venv",
    "node_modules",
    ".mypy_cache",
    ".pytest_cache",
    ".ruff_cache",
    "alembic/versions",  # Don't touch old migrations
    "storage",
    "htmlcov",  # Generated coverage reports
    ".aider.tags.cache.v3",
}

SKIP_FILES = {
    "rename_terminology.py",  # Don't modify this script
    "TERMINOLOGY.md",  # Don't modify the terminology doc
    "t001_rename_merchant_store_to_merchant_store.py",  # Don't modify the new migration
    ".aider.chat.history.md",  # Aider chat history
    ".aider.input.history",
}

# File extensions to process
EXTENSIONS = {".py", ".html", ".js", ".css", ".json", ".yml", ".yaml", ".md",
              ".txt", ".env", ".cfg", ".ini", ".toml", ".sh", ".mak"}
# Also process Makefile (no extension)
EXACT_NAMES = {"Makefile", ".env.example", "Dockerfile", "docker-compose.yml"}

# ============================================================================
# PROTECTED PATTERNS - these must NOT be renamed
# ============================================================================
# These are address/billing fields where "merchant" means "business name on address",
# not our Merchant/Merchant entity. We guard them with placeholders before replacements.
GUARDS = [
    ("ship_company", "ship_company"),
    ("bill_company", "bill_company"),
    # The 'merchant' column on customer_addresses (billing address business name)
    # We need to protect standalone 'merchant' only in specific contexts
    # Pattern: 'merchant = Column(' or '"merchant"' as dict key for address fields
    # We'll handle this with a targeted guard:
    ("company = Column(String(200))", "company = Column(String(200))"),
    ('["company"]', "["company"]"),
    ('buyer_details["company"]', "buyer_details["company"]"),
    ('"company": None', ""company": None"),
    ('"merchant": order.bill_company', ""company": order.bill_company"),
    # Protect letzshop URL paths containing /stores/ (external URLs)
    ("letzshop.lu/vendors/", "letzshop.lu/vendors/"),
    # Protect 'merchant' as a Pydantic field name for addresses
    ("company: str", "company: str"),
    ("company: Optional", "company: Optional"),
    # Protect .merchant attribute access in address context
    ("address.company", "address.company"),
    ("billing.company", "billing.company"),
    ("shipping_address.company", "shipping_address.company"),
]

# ============================================================================
# REPLACEMENT RULES
# ============================================================================
# Order matters! Longest/most specific patterns first to avoid partial matches.
# Each tuple: (old_string, new_string)

REPLACEMENTS = [
    # === COMPOUND CLASS NAMES (longest first) ===

    # Store compound class names -> Store
    ("StoreLetzshopCredentials", "StoreLetzshopCredentials"),
    ("StoreDirectProductCreate", "StoreDirectProductCreate"),
    ("StoreProductCreateResponse", "StoreProductCreateResponse"),
    ("StoreProductListResponse", "StoreProductListResponse"),
    ("StoreProductListItem", "StoreProductListItem"),
    ("StoreProductDetail", "StoreProductDetail"),
    ("StoreProductCreate", "StoreProductCreate"),
    ("StoreProductUpdate", "StoreProductUpdate"),
    ("StoreProductStats", "StoreProductStats"),
    ("StoreProductService", "StoreProductService"),
    ("StoreInvoiceSettings", "StoreInvoiceSettings"),
    ("StoreEmailTemplate", "StoreEmailTemplate"),
    ("StoreEmailSettings", "StoreEmailSettings"),
    ("StoreSubscription", "StoreSubscription"),
    ("StoreNotFoundException", "StoreNotFoundException"),
    ("StoreOnboarding", "StoreOnboarding"),
    ("StoreUserType", "StoreUserType"),
    ("StorePlatform", "StorePlatform"),
    ("StoreDomain", "StoreDomain"),
    ("StoreAddOn", "StoreAddOn"),
    ("StoreTheme", "StoreTheme"),
    ("StoreUser", "StoreUser"),

    # Letzshop-specific store class names
    ("LetzshopStoreDirectorySyncResponse", "LetzshopStoreDirectorySyncResponse"),
    ("LetzshopStoreDirectoryStatsResponse", "LetzshopStoreDirectoryStatsResponse"),
    ("LetzshopStoreDirectoryStats", "LetzshopStoreDirectoryStats"),
    ("LetzshopCreateStoreFromCacheResponse", "LetzshopCreateStoreFromCacheResponse"),
    ("LetzshopCachedStoreListResponse", "LetzshopCachedStoreListResponse"),
    ("LetzshopCachedStoreDetail", "LetzshopCachedStoreDetail"),
    ("LetzshopCachedStoreDetailResponse", "LetzshopCachedStoreDetailResponse"),
    ("LetzshopCachedStoreItem", "LetzshopCachedStoreItem"),
    ("LetzshopStoreSyncService", "LetzshopStoreSyncService"),
    ("LetzshopStoreListResponse", "LetzshopStoreListResponse"),
    ("LetzshopStoreOverview", "LetzshopStoreOverview"),
    ("LetzshopStoreInfo", "LetzshopStoreInfo"),
    ("LetzshopStoreCache", "LetzshopStoreCache"),

    # Service class names
    ("StoreDomainService", "StoreDomainService"),
    ("StoreTeamService", "StoreTeamService"),
    ("StoreService", "StoreService"),

    # Catalog-specific
    ("CatalogStoresResponse", "CatalogStoresResponse"),
    ("CatalogStore", "CatalogStore"),

    # Marketplace-specific
    ("CopyToStoreResponse", "CopyToStoreResponse"),
    ("CopyToStoreRequest", "CopyToStoreRequest"),
    ("StoresResponse", "StoresResponse"),

    # Merchant compound class names -> Merchant
    ("MerchantLoyaltySettings", "MerchantLoyaltySettings"),
    ("MerchantProfileStepStatus", "MerchantProfileStepStatus"),
    ("MerchantProfileResponse", "MerchantProfileResponse"),
    ("MerchantProfileRequest", "MerchantProfileRequest"),
    ("MerchantListResponse", "MerchantListResponse"),
    ("MerchantService", "MerchantService"),
    ("MerchantResponse", "MerchantResponse"),
    ("MerchantCreate", "MerchantCreate"),
    ("MerchantUpdate", "MerchantUpdate"),
    ("MerchantDetail", "MerchantDetail"),
    ("MerchantSchema", "MerchantSchema"),

    # === STANDALONE CLASS NAMES ===
    # Must come after all compound names

    # "Merchant" -> "Merchant" (class name, used in imports, relationships, etc.)
    ("Merchant", "Merchant"),
    # "Store" -> "Store" (class name)
    ("Store", "Store"),

    # === IDENTIFIER PATTERNS (snake_case) ===
    # Longest/most specific first

    # File/module paths in imports
    ("store_product_service", "store_product_service"),
    ("store_product", "store_product"),
    ("store_email_settings_service", "store_email_settings_service"),
    ("store_email_template", "store_email_template"),
    ("store_email_settings", "store_email_settings"),
    ("store_sync_service", "store_sync_service"),
    ("store_domain_service", "store_domain_service"),
    ("store_team_service", "store_team_service"),
    ("store_letzshop_credentials", "store_letzshop_credentials"),
    ("store_invoice_settings", "store_invoice_settings"),
    ("store_subscriptions", "store_subscriptions"),
    ("store_onboarding", "store_onboarding"),
    ("store_platforms", "store_platforms"),
    ("store_platform", "store_platform"),
    ("store_context", "store_context"),
    ("store_domains", "store_domains"),
    ("store_domain", "store_domain"),
    ("store_addons", "store_addons"),
    ("store_themes", "store_themes"),
    ("store_theme", "store_theme"),
    ("store_users", "store_users"),
    ("store_service", "store_service"),
    ("store_memberships", "store_memberships"),
    ("store_auth", "store_auth"),
    ("store_team", "store_team"),
    ("store_profile", "store_profile"),

    # Database column/field identifiers
    ("letzshop_store_slug", "letzshop_store_slug"),
    ("letzshop_store_id", "letzshop_store_id"),
    ("letzshop_store_cache", "letzshop_store_cache"),
    ("claimed_by_store_id", "claimed_by_store_id"),
    ("enrolled_at_store_id", "enrolled_at_store_id"),
    ("store_code", "store_code"),
    ("store_name", "store_name"),
    ("store_id", "store_id"),
    ("store_count", "store_count"),

    # Merchant identifiers
    ("merchant_loyalty_settings", "merchant_loyalty_settings"),
    ("merchant_service", "merchant_service"),
    ("merchant_id", "merchant_id"),

    # Route/URL path segments
    ("admin_stores", "admin_stores"),
    ("admin_store_domains", "admin_store_domains"),
    ("admin_merchants", "admin_merchants"),

    # Generic store/merchant identifiers (MUST be last)
    ("owned_merchants", "owned_merchants"),
    ("active_store_count", "active_store_count"),

    # === DISPLAY TEXT / COMMENTS / STRINGS ===
    # These handle common text patterns in templates, docs, comments

    ("stores", "stores"),
    ("store", "store"),
    ("Stores", "Stores"),

    ("merchants", "merchants"),
    ("merchant", "merchant"),
    ("Merchants", "Merchants"),

    # Title case for display
    ("STORE", "STORE"),
    ("MERCHANT", "MERCHANT"),
]


def should_skip(filepath: str) -> bool:
    """Check if file should be skipped."""
    rel = os.path.relpath(filepath, ROOT)

    # Skip specific files
    basename = os.path.basename(filepath)
    if basename in SKIP_FILES:
        return True

    # Skip directories
    parts = rel.split(os.sep)
    for part in parts:
        if part in SKIP_DIRS:
            return True

    # Check for alembic/versions specifically
    if "alembic" + os.sep + "versions" in rel:
        return True

    return False


def should_process(filepath: str) -> bool:
    """Check if file should be processed based on extension."""
    basename = os.path.basename(filepath)
    if basename in EXACT_NAMES:
        return True
    _, ext = os.path.splitext(filepath)
    return ext in EXTENSIONS


def apply_replacements(content: str) -> str:
    """Apply all replacements to content with guard protection."""
    # Step 1: Apply guards to protect patterns that must NOT change
    for original, guard in GUARDS:
        content = content.replace(original, guard)

    # Step 2: Apply main replacements
    for old, new in REPLACEMENTS:
        content = content.replace(old, new)

    # Step 3: Restore guarded patterns
    for original, guard in GUARDS:
        content = content.replace(guard, original)

    return content


def process_file(filepath: str, dry_run: bool = False) -> tuple[bool, int]:
    """
    Process a single file.
    Returns (changed: bool, num_replacements: int)
    """
    try:
        with open(filepath, "r", encoding="utf-8", errors="replace") as f:
            original = f.read()
    except (OSError, UnicodeDecodeError):
        return False, 0

    modified = apply_replacements(original)

    if modified != original:
        changes = 0
        for old, new in REPLACEMENTS:
            count = original.count(old)
            if count > 0:
                changes += count

        if not dry_run:
            with open(filepath, "w", encoding="utf-8") as f:
                f.write(modified)

        return True, changes

    return False, 0


def rename_files_and_dirs(dry_run: bool = False) -> list[tuple[str, str]]:
    """
    Rename files and directories that contain 'store' or 'merchant' in their names.
    Returns list of (old_path, new_path) tuples.
    """
    renames = []

    # Collect all files/dirs that need renaming (process deepest first for dirs)
    items_to_rename = []

    for dirpath, dirnames, filenames in os.walk(ROOT):
        if should_skip(dirpath):
            dirnames.clear()
            continue

        # Skip hidden dirs and common non-project dirs
        dirnames[:] = [d for d in dirnames if not d.startswith('.') and d not in SKIP_DIRS]

        # Check files
        for fname in filenames:
            if fname in SKIP_FILES:
                continue
            if "store" in fname or "merchant" in fname:
                old_path = os.path.join(dirpath, fname)
                new_name = fname
                new_name = new_name.replace("store_product_service", "store_product_service")
                new_name = new_name.replace("store_product", "store_product")
                new_name = new_name.replace("store_email_settings_service", "store_email_settings_service")
                new_name = new_name.replace("store_email_template", "store_email_template")
                new_name = new_name.replace("store_email_settings", "store_email_settings")
                new_name = new_name.replace("store_sync_service", "store_sync_service")
                new_name = new_name.replace("store_domain_service", "store_domain_service")
                new_name = new_name.replace("store_team_service", "store_team_service")
                new_name = new_name.replace("store_letzshop_credentials", "store_letzshop_credentials")
                new_name = new_name.replace("store_invoice_settings", "store_invoice_settings")
                new_name = new_name.replace("store_subscriptions", "store_subscriptions")
                new_name = new_name.replace("store_onboarding", "store_onboarding")
                new_name = new_name.replace("store_platforms", "store_platforms")
                new_name = new_name.replace("store_platform", "store_platform")
                new_name = new_name.replace("store_context", "store_context")
                new_name = new_name.replace("store_domains", "store_domains")
                new_name = new_name.replace("store_domain", "store_domain")
                new_name = new_name.replace("store_addons", "store_addons")
                new_name = new_name.replace("store_themes", "store_themes")
                new_name = new_name.replace("store_theme", "store_theme")
                new_name = new_name.replace("store_users", "store_users")
                new_name = new_name.replace("store_service", "store_service")
                new_name = new_name.replace("store_auth", "store_auth")
                new_name = new_name.replace("store_team", "store_team")
                new_name = new_name.replace("store_profile", "store_profile")
                new_name = new_name.replace("store", "store")
                new_name = new_name.replace("merchant_service", "merchant_service")
                new_name = new_name.replace("merchant_settings", "merchant_settings")
                new_name = new_name.replace("merchant", "merchant")
                if new_name != fname:
                    new_path = os.path.join(dirpath, new_name)
                    items_to_rename.append((old_path, new_path))

        # Check directories
        for dname in dirnames:
            if "store" in dname or "merchant" in dname:
                # We'll handle directory renames after files
                pass

    # Perform file renames
    for old_path, new_path in items_to_rename:
        rel_old = os.path.relpath(old_path, ROOT)
        rel_new = os.path.relpath(new_path, ROOT)
        if not dry_run:
            os.rename(old_path, new_path)
        renames.append((rel_old, rel_new))

    return renames


def main():
    dry_run = "--dry-run" in sys.argv

    if dry_run:
        print("=== DRY RUN MODE ===\n")

    # Phase 1: Apply text replacements to all files
    print("Phase 1: Applying text replacements...")
    total_files = 0
    changed_files = 0
    total_replacements = 0

    for dirpath, dirnames, filenames in os.walk(ROOT):
        if should_skip(dirpath):
            dirnames.clear()
            continue

        dirnames[:] = [d for d in dirnames if not d.startswith('.') and d not in SKIP_DIRS]

        for fname in filenames:
            filepath = os.path.join(dirpath, fname)
            if not should_process(filepath):
                continue

            total_files += 1
            changed, num = process_file(filepath, dry_run)
            if changed:
                changed_files += 1
                total_replacements += num
                rel = os.path.relpath(filepath, ROOT)
                if dry_run:
                    print(f"  WOULD CHANGE: {rel} ({num} replacements)")

    print(f"\n  Files scanned: {total_files}")
    print(f"  Files {'would be ' if dry_run else ''}changed: {changed_files}")
    print(f"  Total replacements: {total_replacements}")

    # Phase 2: Rename files
    print("\nPhase 2: Renaming files...")
    renames = rename_files_and_dirs(dry_run)
    for old, new in renames:
        action = "WOULD RENAME" if dry_run else "RENAMED"
        print(f"  {action}: {old} -> {new}")
    print(f"\n  Files {'would be ' if dry_run else ''}renamed: {len(renames)}")

    print("\nDone!")


if __name__ == "__main__":
    main()
