import logging
import os
import shutil
import subprocess
import sys
from pathlib import Path

# --- Configuration ---

DEST_ROOT = "/Users/irtaza.akram/openedx/openedx-platform/src/xblocks-contrib/xblocks_contrib/problem"
EDX_PLATFORM_ROOT = "/Users/irtaza.akram/openedx/openedx-platform"

# Python Source Paths (Existing logic)
PYTHON_SOURCE_PATHS = [
    f"{EDX_PLATFORM_ROOT}/openedx/core/djangolib/markup.py",
    f"{EDX_PLATFORM_ROOT}/openedx/core/lib/safe_lxml/xmlparser.py",
    f"{EDX_PLATFORM_ROOT}/xmodule/capa_block.py",
    f"{EDX_PLATFORM_ROOT}/xmodule/capa",
    f"{EDX_PLATFORM_ROOT}/xmodule/stringify.py",
    f"{EDX_PLATFORM_ROOT}/xmodule/tests/test_capa_block.py",
    f"{EDX_PLATFORM_ROOT}/xmodule/tests/test_stringify.py",
]


# Files to explicitly remove before copying
FILES_TO_REMOVE = [
    "problem.py",
    "tests/test_problem.py",
    "templates/problem.html",
    "static/js/src/problem.js",
    "static/css/problem.css",
]

# Static Asset Mappings
# Format: (Source Path, Destination Relative to DEST_ROOT, "file" or "dir" or "merge_dir")
STATIC_ASSET_OPS = [
    (f"{EDX_PLATFORM_ROOT}/common/static/applets/capa", "capa/static/applets", "dir"),
    (f"{EDX_PLATFORM_ROOT}/common/static/common/js/spec_helpers/jasmine-extensions.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/common/js/spec_helpers/jasmine-waituntil.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/images/capa", "capa/static/images", "dir"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/capa", "capa/static/js/capa", "merge_dir"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/src/accessibility_tools.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/src/ajax_prefix.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/src/logger.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/test/add_ajax_prefix.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/test/i18n.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/vendor/codemirror-compressed.js", "assets/static/js/vendor", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/vendor/CodeMirror/octave.js", "capa/static/js/vendor/CodeMirror", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/static/js/vendor/jasmine-imagediff.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/common/test/data/capa", "tests/data/capa", "dir"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/karma_runner.js", "assets", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/spec/capa", "assets/spec", "merge_dir"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/spec/collapsible_spec.js", "assets/spec", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/spec/helper.js", "assets/spec_helpers", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/src/capa", "assets/static/js", "merge_dir"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/src/collapsible.js", "assets/static/js", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/src/javascript_loader.js", "assets/static/js", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/js/src/xmodule.js", "assets/static/js", "file"),
    (f"{EDX_PLATFORM_ROOT}/xmodule/static/css-builtin-blocks/ProblemBlockDisplay.css", "assets/static/css", "file"),
]

# Fixtures to copy individually
FIXTURES_SOURCE_DIR = f"{EDX_PLATFORM_ROOT}/xmodule/js/fixtures"
FIXTURES_DEST_REL = "assets/fixtures"
FIXTURES_FILES = [
    "checkbox_problem.html",
    "codeinput_problem.html",
    "imageinput.html",
    "imageinput.underscore",
    "jsinput_problem.html",
    "matlabinput_problem.html",
    "problem_content_1240.html",
    "problem_content.html",
    "problem.html",
    "radiobutton_problem.html",
]

# Replacements
BASE_REPLACEMENTS = {
    "openedx.core.djangolib.markup": "xblocks_contrib.problem.markup",
    "openedx.core.lib.safe_lxml.xmlparser": "xblocks_contrib.problem.xmlparser",
    "xmodule.capa_block": "xblocks_contrib.problem.capa_block",
    "xmodule.capa": "xblocks_contrib.problem.capa",
    "xmodule.stringify": "xblocks_contrib.problem.stringify",
    "xmodule.tests": "xblocks_contrib.problem.tests",
}

# Tools
ISORT_CMD = ["isort", "--line-length", "120", "xblocks_contrib/problem"]
BLACK_CMD = ["black", "--line-length", "120", "xblocks_contrib/problem"]

# --- Setup Logging ---
logging.basicConfig(
    level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s", handlers=[logging.StreamHandler(sys.stdout)]
)
logger = logging.getLogger()


def run_command(command, working_dir=None):
    """Runs a shell command and fails hard if it errors."""
    cmd_str = " ".join(command)
    logger.info(f"Running command: {cmd_str}")

    try:
        cwd = working_dir if working_dir else os.getcwd()
        result = subprocess.run(command, cwd=cwd, capture_output=True, text=True)

        if result.returncode != 0:
            logger.error(f"Command failed: {cmd_str}")
            logger.error(f"STDOUT: {result.stdout}")
            logger.error(f"STDERR: {result.stderr}")
            sys.exit(1)
        return result.stdout
    except Exception as e:
        logger.error(f"Execution failed for: {cmd_str}")
        logger.exception(e)
        sys.exit(1)


def cleanup_pre_copy():
    """Removes specific files and folders before starting the copy process."""
    logger.info("--- 🧹 Step 0: Pre-Copy Cleanup ---")
    dest_root = Path(DEST_ROOT)

    for rel_path in FILES_TO_REMOVE:
        target = dest_root / rel_path
        if target.exists():
            if target.is_file():
                logger.info(f"Removing file: {target}")
                target.unlink()
            elif target.is_dir():
                logger.info(f"Removing directory: {target}")
                shutil.rmtree(target)
        else:
            logger.info(f"Skipping removal (not found): {target}")


def is_test_file(filename):
    return filename.startswith("test_") or filename.endswith("_test.py") or filename.endswith("tests.py")


def copy_python_sources():
    """Copies Python files and folders."""
    logger.info("--- 🐍 Step 1: Copying Python Sources ---")

    dest_main = Path(DEST_ROOT)
    dest_tests = dest_main / "tests"

    dest_main.mkdir(parents=True, exist_ok=True)
    dest_tests.mkdir(parents=True, exist_ok=True)

    for src_path_str in PYTHON_SOURCE_PATHS:
        src = Path(src_path_str)

        if not src.exists():
            logger.error(f"Source does not exist: {src}")
            sys.exit(1)

        if src.is_dir():
            target_dir = dest_main / src.name
            logger.info(f"Copying directory: {src} -> {target_dir}")
            if target_dir.exists():
                shutil.rmtree(target_dir)
            shutil.copytree(src, target_dir)

        elif src.is_file():
            if is_test_file(src.name):
                target_file = dest_tests / src.name
                logger.info(f"Copying TEST file: {src} -> {target_file}")
            else:
                target_file = dest_main / src.name
                logger.info(f"Copying file: {src} -> {target_file}")
            shutil.copy2(src, target_file)


def copy_static_assets():
    """Copies CSS, JS, Fixtures, and Specs based on new requirements."""
    logger.info("--- 🎨 Step 2: Copying Static Assets (CSS/JS/Fixtures) ---")

    dest_root = Path(DEST_ROOT)

    # 1. Process General Static Assets (CSS, JS Folders)
    for src_path, dest_rel, mode in STATIC_ASSET_OPS:
        src = Path(src_path)
        dest = dest_root / dest_rel

        if not src.exists():
            logger.warning(f"Static source not found: {src}")
            continue

        if mode == "file":
            dest.mkdir(parents=True, exist_ok=True)
            shutil.copy2(src, dest / src.name)
            logger.info(f"Copied file: {src.name} -> {dest}")

        elif mode == "dir":
            if dest.exists():
                shutil.rmtree(dest)
            shutil.copytree(src, dest)
            logger.info(f"Copied directory: {src} -> {dest}")

        elif mode == "merge_dir":
            # dirs_exist_ok=True allows merging contents into an existing folder (Python 3.8+)
            dest.mkdir(parents=True, exist_ok=True)
            shutil.copytree(src, dest, dirs_exist_ok=True)
            logger.info(f"Merged directory: {src} -> {dest}")

    # 2. Process Fixtures (Specific list of files)
    fixtures_src = Path(FIXTURES_SOURCE_DIR)
    fixtures_dest = dest_root / FIXTURES_DEST_REL
    fixtures_dest.mkdir(parents=True, exist_ok=True)

    logger.info(f"Copying {len(FIXTURES_FILES)} fixtures to {fixtures_dest}...")

    for fname in FIXTURES_FILES:
        f_src = fixtures_src / fname
        if f_src.exists():
            shutil.copy2(f_src, fixtures_dest / fname)
        else:
            logger.warning(f"Fixture not found: {fname}")


def apply_replacements():
    """Reads all files in destination and applies substitutions."""
    logger.info("--- 📝 Step 3: Refactoring Imports ---")

    dest_path = Path(DEST_ROOT)
    sorted_replacements = dict(sorted(BASE_REPLACEMENTS.items(), key=lambda item: len(item[0]), reverse=True))

    for file_path in dest_path.rglob("*.py"):
        try:
            with open(file_path, "r", encoding="utf-8") as f:
                content = f.read()

            original_content = content
            changes_count = 0

            for old_str, new_str in sorted_replacements.items():
                if old_str in content:
                    count = content.count(old_str)
                    content = content.replace(old_str, new_str)
                    changes_count += count

            if content != original_content:
                logger.info(f"  Fixed {changes_count} refs in: {file_path.name}")
                with open(file_path, "w", encoding="utf-8") as f:
                    f.write(content)

        except Exception as e:
            logger.error(f"❌ Failed processing file: {file_path}")
            sys.exit(1)


def run_tools():
    """Runs isort, black, and pylint."""
    logger.info("--- 🛠 Step 4: Running Code Quality Tools ---")

    work_dir = Path(DEST_ROOT).parent.parent

    run_command(BLACK_CMD, working_dir=work_dir)
    run_command(ISORT_CMD, working_dir=work_dir)


def main():
    logger.info("Script started.")

    cleanup_pre_copy()
    copy_python_sources()
    copy_static_assets()
    apply_replacements()
    run_tools()

    logger.info("All steps completed successfully.")


if __name__ == "__main__":
    main()
