"""
Fleet control endpoint.

A tenant install POSTs a signed JSON payload here on a schedule. Final Target
records what it reports, then answers with the directives that tenant should
apply: which plan it is on, which modules should be enabled, and which FT OS
version it should be running.

Nothing here trusts the request until the HMAC signature is verified against
the api_key stored on the Tenant row.
"""
import hashlib
import json

from django.http import JsonResponse
from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from apps.tenants.models import Tenant, TenantModule, MODULE_CHOICES, PLAN_MODULES, ALWAYS_ON_MODULES
from .models import CheckinLog
from .signing import verify


def _client_ip(request):
    forwarded = request.META.get('HTTP_X_FORWARDED_FOR', '')
    if forwarded:
        return forwarded.split(',')[0].strip()
    return request.META.get('REMOTE_ADDR') or None


def _error(message, status=400):
    return JsonResponse({'ok': False, 'error': message}, status=status)


@csrf_exempt
@require_POST
def checkin(request):
    slug = request.headers.get('X-FT-Tenant', '').strip()
    timestamp = request.headers.get('X-FT-Timestamp', '')
    signature = request.headers.get('X-FT-Signature', '')

    if not slug:
        return _error('missing X-FT-Tenant header')

    tenant = Tenant.objects.filter(slug=slug).first()
    if tenant is None:
        return _error('unknown tenant', status=404)

    raw = request.body
    ok, reason = verify(tenant.api_key, raw, timestamp, signature)
    if not ok:
        return _error(reason, status=401)

    try:
        payload = json.loads(raw.decode('utf-8') or '{}')
    except ValueError:
        return _error('body is not valid JSON')

    # Record what the tenant reported
    tenant.reported_version = str(payload.get('ft_version', ''))[:20]
    tenant.os_name = str(payload.get('os_name', ''))[:60]
    tenant.os_version = str(payload.get('os_version', ''))[:40]
    tenant.python_version = str(payload.get('python_version', ''))[:20]
    tenant.django_version = str(payload.get('django_version', ''))[:20]
    tenant.db_engine = str(payload.get('db_engine', ''))[:30]
    tenant.last_checkin_at = timezone.now()
    tenant.save(update_fields=[
        'reported_version', 'os_name', 'os_version', 'python_version',
        'django_version', 'db_engine', 'last_checkin_at',
    ])

    # Work out what this tenant should be running
    valid_plans = {key for key, _ in Tenant.PLAN_CHOICES}
    plan = tenant.plan if tenant.plan in valid_plans else Tenant.PLAN_STARTER

    enabled = set(tenant.enabled_module_keys)
    if not enabled:
        # Nothing recorded centrally yet, fall back to what the plan grants
        enabled = set(PLAN_MODULES.get(plan, set())) | set(ALWAYS_ON_MODULES)

    # Panel logo. The URL alone is not enough, because a tenant cannot tell a
    # changed image from the same one without downloading it every time. The
    # digest lets the client compare first and fetch only when it differs.
    def _image_directive(field):
        """Absolute URL plus a digest, or two blanks if there is no usable file."""
        if not field:
            return '', ''
        try:
            digest = hashlib.sha256()
            with field.open('rb') as fh:
                for chunk in iter(lambda: fh.read(65536), b''):
                    digest.update(chunk)
            return request.build_absolute_uri(field.url), digest.hexdigest()
        except Exception:
            # A missing file on disk must not break the whole check-in.
            return '', ''

    logo_url, logo_sig = _image_directive(tenant.logo)
    favicon_url, favicon_sig = _image_directive(tenant.favicon)

    all_keys = {key for key, _ in MODULE_CHOICES}
    directives = {
        'plan': plan,
        'modules_enabled': sorted(enabled & all_keys),
        'modules_disabled': sorted(all_keys - enabled - set(ALWAYS_ON_MODULES)),
        # The version this client is entitled to, set when an upgrade is
        # granted or billed on the Tenants page. One value, one place.
        'target_version': tenant.cms_version or '',
        'accent_colour': tenant.accent_colour,
        'cms_dark_colour': tenant.cms_dark_colour,
        'cms_text_colour': tenant.cms_text_colour,
        'site_name': tenant.name,
        'logo_url': logo_url,
        'logo_sig': logo_sig,
        'favicon_url': favicon_url,
        'favicon_sig': favicon_sig,
    }

    CheckinLog.objects.create(
        tenant=tenant,
        ft_version=tenant.reported_version,
        os_label=tenant.os_label[:100],
        db_engine=tenant.db_engine,
        plan=plan,
        module_count=len(directives['modules_enabled']),
        directives=directives,
        source_ip=_client_ip(request),
    )

    return JsonResponse({'ok': True, 'directives': directives})
