"""
Build the CNPERP local-server ZIP package for one-click download from the website.
"""
from __future__ import annotations

import os
import zipfile
from datetime import datetime
from pathlib import Path
from typing import Tuple

_REPO_ROOT = Path(__file__).resolve().parents[2]

_PACKAGE_TOP_LEVEL = (
    "app",
    "deploy",
    "migrations",
    "scripts",
    "requirements.txt",
    "version.txt",
)

_SKIP_DIR_NAMES = {
    ".git",
    ".venv",
    "__pycache__",
    ".pytest_cache",
    "node_modules",
    "dist",
    "logs",
    ".mypy_cache",
    ".ruff_cache",
}

_SKIP_FILE_SUFFIXES = (".pyc", ".pyo", ".log")

_PACKAGE_FOLDER = "CNPERP"

_GET_STARTED = """CNPERP Local Server — Quick Start
================================

This ZIP contains a folder named CNPERP. Extract it so the app lives at:

  Windows:  C:\\CNPERP\\app\\main.py
  Linux:    /opt/CNPERP/app/main.py   (or ~/CNPERP on a small office PC)
  macOS:    ~/CNPERP/app/main.py

The local server runs on this PC and serves your LAN. Sales, invoices, and
journal entries sync to the CNP cloud hub in near real time (seconds).

── Windows ──────────────────────────────────────────────────────────
1. Extract ZIP to C:\\  →  you get C:\\CNPERP\\
2. PowerShell as Administrator:
     cd C:\\CNPERP
     Set-ExecutionPolicy -Scope Process Bypass
     .\\deploy\\local-server\\Install-CNPERP-Local.ps1
3. Desktop shortcut "CNPERP Server" — keep the window open
4. "CNPERP Setup Wizard" — license key, then company setup

── Linux (Ubuntu) ───────────────────────────────────────────────────
1. sudo unzip cnperp-local-install-*.zip -d /opt
2. cd /opt/CNPERP/deploy/local-server
     chmod +x *.sh
     sudo ./install-cnperp-local.sh --install-service
   Or without systemd: ./install-cnperp-local.sh
3. ./start-cnperp-server.sh   (or: sudo systemctl start cnperp-local)
4. Browser: http://localhost:8010/setup

── macOS ────────────────────────────────────────────────────────────
1. unzip cnperp-local-install-*.zip -d ~
2. cd ~/CNPERP/deploy/local-server && chmod +x *.sh
     ./install-cnperp-local.sh
3. ./start-cnperp-server.sh
4. Browser: http://localhost:8010/setup

── Cloud link (all platforms) ───────────────────────────────────────
After setup, add license_key to deploy/license-node.json, then:
  Windows: .\\deploy\\local-server\\Connect-Cloud.ps1
  Linux/Mac: ./deploy/local-server/connect-cloud.sh

Requirements: Python 3.10+, PostgreSQL 14+, 4 GB RAM
LAN users: http://<server-ip>:8010

CNP Solutions — https://erp.cnpsolutions.co.bw
"""


def read_version() -> str:
    ver_file = _REPO_ROOT / "version.txt"
    if ver_file.is_file():
        text = ver_file.read_text(encoding="utf-8").strip()
        if text:
            return text
    return datetime.utcnow().strftime("%Y%m%d")


def package_filename(version: str | None = None) -> str:
    return f"cnperp-local-install-{version or read_version()}.zip"


def _should_skip(path: Path) -> bool:
    for part in path.parts:
        if part in _SKIP_DIR_NAMES:
            return True
    if path.suffix.lower() in _SKIP_FILE_SUFFIXES:
        return True
    return False


def build_package_zip(dest_dir: Path | None = None) -> Tuple[str, Path]:
    """Create ZIP under dist/ (or dest_dir). Returns (filename, absolute path)."""
    version = read_version()
    filename = package_filename(version)
    out_dir = dest_dir or (_REPO_ROOT / "dist")
    out_dir.mkdir(parents=True, exist_ok=True)
    zip_path = out_dir / filename

    prefix = f"{_PACKAGE_FOLDER}/"

    with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED, compresslevel=6) as zf:
        zf.writestr(f"{prefix}GET_STARTED.txt", _GET_STARTED)
        for item in _PACKAGE_TOP_LEVEL:
            src = _REPO_ROOT / item
            if not src.exists():
                continue
            if src.is_file():
                zf.write(src, f"{prefix}{item}")
            else:
                for root, dirs, files in os.walk(src):
                    dirs[:] = [d for d in dirs if d not in _SKIP_DIR_NAMES]
                    for name in files:
                        fp = Path(root) / name
                        if _should_skip(fp):
                            continue
                        arc = prefix + fp.relative_to(_REPO_ROOT).as_posix()
                        zf.write(fp, arc)

    return filename, zip_path


def _latest_source_mtime() -> float:
    """Return latest mtime across package source inputs."""
    latest = 0.0
    for item in _PACKAGE_TOP_LEVEL:
        src = _REPO_ROOT / item
        if not src.exists():
            continue
        if src.is_file():
            latest = max(latest, src.stat().st_mtime)
            continue

        for root, dirs, files in os.walk(src):
            dirs[:] = [d for d in dirs if d not in _SKIP_DIR_NAMES]
            for name in files:
                fp = Path(root) / name
                if _should_skip(fp):
                    continue
                latest = max(latest, fp.stat().st_mtime)
    return latest


def get_package_path(force_rebuild: bool = False) -> Tuple[str, Path]:
    """
    Return install ZIP path.

    Auto-rebuilds when:
    - package is missing
    - caller sets force_rebuild=True
    - any packaged source file is newer than the ZIP
    """
    version = read_version()
    filename = package_filename(version)
    zip_path = _REPO_ROOT / "dist" / filename

    if zip_path.is_file():
        if force_rebuild:
            zip_path.unlink()
        else:
            latest_src_mtime = _latest_source_mtime()
            if zip_path.stat().st_mtime >= latest_src_mtime:
                return filename, zip_path

    return build_package_zip()
