# -*- coding: utf-8 -*-

import fcntl
import os
import shutil
import subprocess

from . import flock
from .paths import HOSTS_FILE, HOSTS_LOCK, LXC_ROOT
from .templates import LXC_CONTAINER

def prepare_container(container, layers):
    # Remove ephemeral layer data
    clean_ephemeral_layer(container)
    # Prepare and mount overlayfs
    rootfs = os.path.join(LXC_ROOT, container, 'rootfs')
    # Unmount rootfs in case it remained mounted for whatever reason
    subprocess.run(['umount', rootfs])
    layers = layers.split(',')
    if len(layers) == 1:
        # We have only single layer, no overlay needed
        subprocess.run(['mount', '--bind', layers[0], rootfs])
    else:
        olwork = os.path.join(LXC_ROOT, container, 'olwork')
        subprocess.run(['mount', '-t', 'overlay', '-o', 'upperdir={},lowerdir={},workdir={}'.format(layers[-1], ':'.join(layers[:-1]), olwork), 'none', rootfs])

def clean_ephemeral_layer(container):
    # Cleans containers ephemeral layer. Called in lxc.hook.post-stop and lxc.hook.pre-start in case of unclean shutdown
    # This is done early in the container start process, so the inode of the ephemeral directory must remain unchanged
    ephemeral = os.path.join(LXC_ROOT, container, 'ephemeral')
    for item in os.scandir(ephemeral):
        shutil.rmtree(item.path) if item.is_dir() else os.unlink(item.path)

def cleanup_container(container):
    # Unmount rootfs
    rootfs = os.path.join(LXC_ROOT, container, 'rootfs')
    subprocess.run(['umount', rootfs])
    # Remove ephemeral layer data
    clean_ephemeral_layer(container)

def create_container(container, image):
    # Create directories after container installation
    rootfs = os.path.join(LXC_ROOT, container, 'rootfs')
    olwork = os.path.join(LXC_ROOT, container, 'olwork')
    ephemeral = os.path.join(LXC_ROOT, container, 'ephemeral')
    os.makedirs(rootfs, 0o755, True)
    os.makedirs(olwork, 0o755, True)
    os.makedirs(ephemeral, 0o755, True)
    os.chown(ephemeral, 100000, 100000)
    # Create container configuration file
    layers = ','.join([os.path.join(LXC_STORAGE_DIR, layer) for layer in image['layers']])
    if 'build' not in image:
        layers = '{},{}'.format(layer, ephemeral)
    mounts = '\n'.join(['lxc.mount.entry = {} {} none bind,create={} 0 0'.format(m[1], m[2].lstrip('/'), m[0].lower()) for m in image['mounts']]) if 'mounts' in image else ''
    env = '\n'.join(['lxc.environment = {}={}'.format(e[0], e[1]) for e in image['env']]) if 'env' in image else ''
    uid = image['uid'] if 'uid' in image else '0'
    gid = image['gid'] if 'gid' in image else '0'
    cmd = image['cmd'] if 'cmd' in image else '/bin/sh'
    cwd = image['cwd'] if 'cwd' in image else '/'
    halt = image['halt'] if 'halt' in image else 'SIGINT'
    # Lease the first unused IP to the container
    ipv4 = update_hosts_lease(container, True)
    # Create the config file
    with open(os.path.join(LXC_ROOT, container, 'config'), 'w') as f:
        f.write(LXC_CONTAINER.format(name=container, ipv4=ipv4, layers=layers, mounts=mounts, env=env, uid=uid, gid=gid, cmd=cmd, cwd=cwd, halt=halt))

def destroy_container(container):
    # Remove container configuration and directories
    shutil.rmtree(os.path.join(LXC_ROOT, container))
    # Release the IP address
    update_hosts_lease(container, False)

@flock.flock_ex(HOSTS_LOCK)
def update_hosts_lease(container, is_request):
    # This is a poor man's DHCP server which uses /etc/hosts as lease database
    # Leases the first unused IP from range 172.17.0.0/16
    # Uses file lock as interprocess mutex
    ip = None
    # Load all existing records
    with open(HOSTS_FILE, 'r') as f:
        leases = [l.strip().split(' ', 1) for l in f]
    # If this call is a request for lease, find the first unassigned IP
    if is_request:
        used_ips = [l[0] for l in leases]
        for i in range(2, 65278): # Reserve last /24 subnet for VPN
            ip = '172.17.{}.{}'. format(i // 256, i % 256)
            if ip not in used_ips:
                leases.append([ip, container])
                break
    # Otherwise it is a release in which case we just delete the record
    else:
        leases = [l for l in leases if l[1] != container]
    # Write the contents back to the file
    with open(HOSTS_FILE, 'w') as f:
        for lease in leases:
            f.write('{} {}\n'.format(lease[0], lease[1]))
    return ip
