from __future__ import annotations

import importlib.util
import os
import shutil
import subprocess
import sys
from pathlib import Path


PROJECT_DIR = Path(__file__).resolve().parent.parent
DIST_DIR = PROJECT_DIR / "dist"
BUILD_DIR = PROJECT_DIR / "build"
APP_NAME = "SunnyERPApp"
APP_BUNDLE = DIST_DIR / f"{APP_NAME}.app"
SPEC_FILE = PROJECT_DIR / f"{APP_NAME}_mac.spec"
EXECUTABLE_PATH = APP_BUNDLE / "Contents" / "MacOS" / APP_NAME


def app_version() -> str:
    version_file = PROJECT_DIR / "app_core" / "version.py"
    for line in version_file.read_text(encoding="utf-8").splitlines():
        if line.startswith("APP_VERSION"):
            return line.split("=", 1)[1].strip().strip('"')
    return "unknown"


def package_suffix() -> str:
    suffix = str(os.environ.get("BUILD_MAC_PACKAGE_SUFFIX", "") or "").strip()
    if not suffix:
        return ""
    if suffix.startswith("_"):
        return suffix
    return f"_{suffix}"


def package_zip_path() -> Path:
    return DIST_DIR / f"{APP_NAME}_macOS{package_suffix()}.zip"


def target_arch() -> str:
    return str(os.environ.get("BUILD_MAC_TARGET_ARCH", "") or "").strip()


def ensure_macos() -> None:
    if sys.platform != "darwin":
        raise RuntimeError("build_mac.py must be run on macOS.")


def ensure_build_dependencies() -> None:
    required_modules = {
        "PyInstaller": "PyInstaller",
        "pdfplumber": "pdfplumber",
        "openpyxl": "openpyxl",
        "et_xmlfile": "et_xmlfile",
    }
    missing = [package_name for module_name, package_name in required_modules.items() if importlib.util.find_spec(module_name) is None]
    if missing:
        raise RuntimeError(
            "Missing build dependencies: "
            + ", ".join(missing)
            + ". Install them in the Python environment used to run build_mac.py."
        )


def run(command: list[str]) -> None:
    subprocess.run(command, cwd=PROJECT_DIR, check=True)


def ensure_clean_dirs() -> None:
    package_zip = package_zip_path()
    if BUILD_DIR.exists():
        shutil.rmtree(BUILD_DIR)
    if APP_BUNDLE.exists():
        shutil.rmtree(APP_BUNDLE)
    if package_zip.exists():
        package_zip.unlink()


def copy_optional_file(source: Path, target: Path) -> None:
    if source.exists():
        shutil.copy2(source, target)


def write_tester_note(target_dir: Path) -> None:
    arch_suffix = target_arch() or "default"
    version = app_version()
    tester_note = target_dir / "RUN_FOR_TESTERS_macOS.txt"
    tester_note.write_text(
        "\n".join(
            [
                "CitiFox ERP App for macOS",
                "",
                "How to run:",
                f"- Download the correct artifact for the Mac CPU: arm64 for Apple Silicon, x64 for Intel.",
                f"- Unzip SunnyERPApp_macOS_*.zip.",
                f"- The zip contains one macOS app bundle: {APP_NAME}.app.",
                f"- Open {APP_NAME}.app. Do not run the Windows .exe file on macOS.",
                "",
                "Build info:",
                f"- App version: {version}",
                f"- Target arch: {arch_suffix}",
                "",
                "Notes:",
                "- App data is stored in ~/sunny_erp_local",
                "- Startup debug log: ~/sunny_erp_local/debug/startup_debug.log",
                "- To open the debug folder from Terminal: open ~/sunny_erp_local/debug",
                "- This test app is not Apple-notarized yet. If macOS says Apple cannot verify it, click Done.",
                "- Then open System Settings > Privacy & Security and click Open Anyway for SunnyERPApp.",
                "- Alternative Terminal command: xattr -dr com.apple.quarantine /path/to/SunnyERPApp.app",
                "- You can drag SunnyERPApp.app into Terminal after typing: xattr -dr com.apple.quarantine ",
                "- Please send back feedback about import behavior, search, sort, export, and layout.",
            ]
        ),
        encoding="utf-8",
    )


def verify_build_outputs() -> None:
    if not APP_BUNDLE.exists():
        raise RuntimeError(f"App bundle was not created: {APP_BUNDLE}")
    if not EXECUTABLE_PATH.exists():
        raise RuntimeError(f"App executable is missing: {EXECUTABLE_PATH}")
    run(["file", str(EXECUTABLE_PATH)])


def sign_app_bundle() -> None:
    # Ad-hoc signing gives the bundle a valid local code signature. The app is
    # still not Apple-notarized, so first launch may require Open Anyway.
    run(["codesign", "--force", "--deep", "--sign", "-", "--timestamp=none", str(APP_BUNDLE)])
    run(["codesign", "--verify", "--deep", "--strict", str(APP_BUNDLE)])


def zip_distribution() -> None:
    package_zip = package_zip_path()
    # Use macOS-native packaging to preserve bundle metadata and symlinks.
    run(
        [
            "ditto",
            "-c",
            "-k",
            "--sequesterRsrc",
            "--keepParent",
            str(APP_BUNDLE),
            str(package_zip),
        ]
    )


def build() -> Path:
    ensure_macos()
    ensure_build_dependencies()
    DIST_DIR.mkdir(parents=True, exist_ok=True)
    ensure_clean_dirs()

    command = [
        sys.executable,
        "-m",
        "PyInstaller",
        "--noconfirm",
        "--clean",
        str(SPEC_FILE),
    ]
    run(command)

    copy_optional_file(PROJECT_DIR / "README.md", DIST_DIR / "README.md")
    write_tester_note(DIST_DIR)
    verify_build_outputs()
    sign_app_bundle()
    zip_distribution()
    return APP_BUNDLE


if __name__ == "__main__":
    output_path = build()
    print(output_path)
