#!/usr/bin/env python3
# deploy.py — Deploy CMS from tests to stage or prod.
# Usage:
#   python3 deploy.py stage [--first-deploy]
#   python3 deploy.py prod  [--first-deploy]
#
# Requires deploy.config.json in the same directory. Not tracked in git.

import os
import sys
import json
import shutil
import datetime

# ---------------------------------------------------------------------------
# Configuration
# ---------------------------------------------------------------------------

SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
CONFIG_PATH = os.path.join(SCRIPT_DIR, 'deploy.config.json')

if not os.path.isfile(CONFIG_PATH):
    print('ERROR: deploy.config.json not found in {}'.format(SCRIPT_DIR))
    print('Create it with keys: source, stage, prod, backups')
    sys.exit(1)

with open(CONFIG_PATH) as f:
    _config = json.load(f)

def _require(key):
    if key not in _config:
        print('ERROR: missing key "{}" in deploy.config.json'.format(key))
        sys.exit(1)
    return _config[key]

SOURCE      = _require('source')
STAGE_DIR   = _require('stage')
PROD_DIR    = _require('prod')
BACKUPS_DIR = _require('backups')
IS_LOCAL    = _config.get('local', False)

# Directories inside tests/ that are NOT clients
NON_CLIENT_DIRS = {
    'app', 'archive', 'raw-images', '__pycache__', '_template'
}

# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def log(msg):
    print('[deploy] ' + msg)

def detect_clients(source):
    """Return sorted list of client directory names found in source."""
    clients = []
    for name in sorted(os.listdir(source)):
        if name in NON_CLIENT_DIRS:
            continue
        path = os.path.join(source, name)
        if not os.path.isdir(path):
            continue
        if not os.path.isfile(os.path.join(path, 'content.json')):
            continue
        clients.append(name)
    return clients

def find_editor_dir(client_src):
    """Find the editor bootstrap directory inside a client directory.
    It is the only subdirectory that is not 'images/' and contains index.html."""
    for name in sorted(os.listdir(client_src)):
        if name == 'images':
            continue
        path = os.path.join(client_src, name)
        if os.path.isdir(path) and os.path.isfile(os.path.join(path, 'index.html')):
            return name
    return None

def backup(target_dir, target_name):
    """Create a timestamped backup of the target directory."""
    if not os.path.exists(target_dir) or not os.listdir(target_dir):
        log('Target is empty — skipping backup.')
        return
    timestamp = datetime.datetime.now().strftime('%Y%m%d_%H%M%S')
    backup_name = '{}_{}'.format(target_name, timestamp)
    backup_path = os.path.join(BACKUPS_DIR, backup_name)
    os.makedirs(BACKUPS_DIR, exist_ok=True)
    shutil.copytree(target_dir, backup_path)
    log('Backup created: backups/{}/'.format(backup_name))

def copy_tree(src, dst):
    """Copy src directory tree to dst, replacing it if it exists."""
    if os.path.exists(dst):
        shutil.rmtree(dst)
    shutil.copytree(src, dst)

def copy_file(src, dst):
    """Copy a single file, creating parent directories as needed."""
    os.makedirs(os.path.dirname(dst), exist_ok=True)
    shutil.copy2(src, dst)

def chmod_cgi(target_dir):
    """Set chmod 755 on all CGI scripts in app/cgi-bin/."""
    cgi_dir = os.path.join(target_dir, 'app', 'cgi-bin')
    if not os.path.isdir(cgi_dir):
        log('WARNING: cgi-bin not found at {}'.format(cgi_dir))
        return
    for name in os.listdir(cgi_dir):
        if name.endswith('.cgi'):
            path = os.path.join(cgi_dir, name)
            os.chmod(path, 0o755)
            log('chmod 755: app/cgi-bin/{}'.format(name))

def check_types_json(source, target_dir):
    """Warn if types.json differs between source and target."""
    src_path = os.path.join(source, 'app', 'types.json')
    dst_path = os.path.join(target_dir, 'app', 'types.json')
    if not os.path.isfile(dst_path):
        return  # first deploy, nothing to compare
    with open(src_path) as f:
        src_content = f.read()
    with open(dst_path) as f:
        dst_content = f.read()
    if src_content != dst_content:
        print()
        print('  WARNING: types.json has changed since last deploy.')
        print('  Verify whether content.json on {} requires migration.'.format(
            os.path.basename(target_dir)))
        print()

def read_version(source):
    """Read the VERSION file from source."""
    version_path = os.path.join(source, 'VERSION')
    if os.path.isfile(version_path):
        with open(version_path) as f:
            return f.read().strip()
    return 'unknown'

# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------

def main():
    args = sys.argv[1:]

    if not args or args[0] not in ('stage', 'prod'):
        print('Usage: python3 deploy.py <stage|prod> [--first-deploy]')
        sys.exit(1)

    target_name  = args[0]
    first_deploy = '--first-deploy' in args
    target_dir   = STAGE_DIR if target_name == 'stage' else PROD_DIR
    version      = read_version(SOURCE)

    # Prod safety confirmation
    if target_name == 'prod':
        print()
        print('  You are about to deploy to PROD (visitenosso.site).')
        print('  Current VERSION: {}'.format(version))
        print('  Type "deploy prod" to confirm, or anything else to cancel:')
        print()
        answer = input('  > ')
        if answer.strip() != 'deploy prod':
            print('  Cancelled.')
            sys.exit(0)
        print()

    log('Deploying VERSION {} to {}'.format(version, target_name.upper()))
    log('Source:      {}'.format(SOURCE))
    log('Destination: {}'.format(target_dir))
    if first_deploy:
        log('Mode: FIRST DEPLOY — content.json and images will be seeded')
    print()

    # Detect clients
    clients = detect_clients(SOURCE)
    log('Clients detected: {}'.format(', '.join(clients)))
    print()

    # Warn about types.json changes before anything is modified
    check_types_json(SOURCE, target_dir)

    # Backup current target
    backup(target_dir, target_name)
    print()

    # Copy app/
    log('Copying app/...')
    copy_tree(
        os.path.join(SOURCE, 'app'),
        os.path.join(target_dir, 'app')
    )

    # Copy VERSION
    copy_file(
        os.path.join(SOURCE, 'VERSION'),
        os.path.join(target_dir, 'VERSION')
    )
    log('Copied VERSION ({})'.format(version))
    print()

    # Per-client files
    for client in clients:
        client_src = os.path.join(SOURCE, client)
        client_dst = os.path.join(target_dir, client)
        os.makedirs(client_dst, exist_ok=True)

        # Public shell
        copy_file(
            os.path.join(client_src, 'index.html'),
            os.path.join(client_dst, 'index.html')
        )

        # Editor bootstrap (obfuscated directory)
        editor_dir = find_editor_dir(client_src)
        if editor_dir:
            copy_file(
                os.path.join(client_src, editor_dir, 'index.html'),
                os.path.join(client_dst, editor_dir, 'index.html')
            )

        # Dev-managed content (every deploy)
        for fname in ('config.json', 'translations.json'):
            src_file = os.path.join(client_src, fname)
            if os.path.isfile(src_file):
                copy_file(src_file, os.path.join(client_dst, fname))

        # First deploy only: seed content.json and images/
        if first_deploy:
            content_src = os.path.join(client_src, 'content.json')
            if os.path.isfile(content_src):
                copy_file(content_src, os.path.join(client_dst, 'content.json'))

            images_src = os.path.join(client_src, 'images')
            images_dst = os.path.join(client_dst, 'images')
            if os.path.isdir(images_src):
                copy_tree(images_src, images_dst)
            else:
                os.makedirs(images_dst, exist_ok=True)

        log('Client {}: done'.format(client))

    print()

    # chmod 755 on CGI files
    chmod_cgi(target_dir)
    print()

    log('Deploy complete. VERSION {} is live on {}.'.format(version, target_name.upper()))

    if IS_LOCAL:
        print()
        print('  Reminder: server.py is not copied automatically.')
        print('  To serve {} locally:'.format(target_name))
        print('    python3 server.py 8081 {}'.format(target_dir))

if __name__ == '__main__':
    main()
