'''ServiceLink mesh endpoint for the picoshare/NanoShare node.

Lets other nodes (browser-cli, website) push files in and read file metadata
over the shared servicelink envelope at POST /rpc, alongside the existing web
UI and /api routes.

Every call needs a bearer token carrying the `files` or `mesh` scope; the endpoint is rate
limited and body-size capped. Keep /rpc on the internal node network.
'''
from __future__ import annotations

import base64
import os

from pathlib import PurePosixPath

from quart import current_app

from my_modules.app.setup import LIMITER
from my_modules.expiry import ensure_utc, parse_expires
from my_modules.file_meta import format_size, iso_stamp_filename
from servicelink import Forbidden, InvalidParams, NotFound, Router, Unauthorized, any_verifier, bearer_verifier, create_link_blueprint, shared_secret_verifier

MAX_RPC_BODY = 16 * 1024 * 1024
RPC_SCOPES = ('files', 'mesh')

router = Router('picoshare')

def _safe_file_path(value: str) -> str:
  raw = (value or '').replace('\\', '/')
  parts = [
    part for part in PurePosixPath(raw).parts
    if part not in ('', '.', '..', '/')
  ]
  return '/'.join(parts)

def _user_id(ctx):
  if ctx.principal is None:
    raise Unauthorized('authentication required')
  if not any(ctx.principal.has_scope(scope) for scope in RPC_SCOPES):
    raise Forbidden('missing required scope: files')
  return ctx.principal.subject

@router.method('files.upload')
async def files_upload(params, ctx):
  user_id = _user_id(ctx)
  text = params.get('text')
  content_b64 = params.get('content_b64')
  if content_b64:
    data = base64.b64decode(content_b64)
    content_type = params.get('content_type') or 'application/octet-stream'
    default_ext = 'bin'
  elif text is not None:
    data = text.encode('utf-8')
    content_type = 'text/plain'
    default_ext = 'txt'
  else:
    raise InvalidParams('provide text or content_b64')

  file_name = PurePosixPath(str(params.get('file_name') or iso_stamp_filename('mesh', default_ext)).replace('\\', '/')).name
  file_path = _safe_file_path(str(params.get('file_path') or ''))
  storage_id = await current_app.convex.send_to_storage(data=data, content_type=content_type)
  file_record = await current_app.convex.add_file(
    file_name=file_name,
    file_path=file_path,
    file_size=format_size(len(data)),
    note=params.get('note', ''),
    content_type=content_type,
    expires_at=ensure_utc(parse_expires(params.get('expires', ''))),
    storage_id=storage_id,
    user_id=user_id,
  )
  file_id = file_record.get('file_id') if isinstance(file_record, dict) else None
  return {'file_id': file_id, 'file_name': file_name, 'file_path': file_path, 'size': len(data), 'content_type': content_type}

@router.method('files.list')
async def files_list(params, ctx):
  return {'files': await current_app.convex.get_files(user_id=_user_id(ctx))}

@router.method('files.get')
async def files_get(params, ctx):
  file_id = params.get('file_id')
  if not file_id:
    raise InvalidParams('file_id is required')
  meta = await current_app.convex.get_file(file_id)
  if not meta:
    raise NotFound('no such file', data={'file_id': file_id})
  return meta

@router.method('files.info')
async def files_info(params, ctx):
  file_id = params.get('file_id')
  if not file_id:
    raise InvalidParams('file_id is required')
  return await current_app.convex.get_file_informations(file_id, _user_id(ctx))

@router.method('files.update')
async def files_update(params, ctx):
  file_id = params.get('file_id')
  file_name = PurePosixPath(str(params.get('file_name') or '').replace('\\', '/')).name
  if not file_id or not file_name:
    raise InvalidParams('file_id and file_name are required')
  await current_app.convex.update_file(
    file_id=file_id,
    file_name=file_name,
    file_path=_safe_file_path(str(params.get('file_path') or '')),
    note=params.get('note') if 'note' in params else None,
    expires_at=ensure_utc(parse_expires(params.get('expires', ''))),
    user_id=_user_id(ctx),
    preserve_missing=True,
  )
  return {'updated': True}

@router.method('files.delete')
async def files_delete(params, ctx):
  file_id = params.get('file_id')
  if not file_id:
    raise InvalidParams('file_id is required')
  await current_app.convex.delete_file(file_id, _user_id(ctx))
  return {'deleted': True}

async def _decode_access_token(token):
  payload = await current_app.convex.decode_access_token_payload(access_token=token)
  if not payload or payload.get('error') or not payload.get('sub'):
    raise ValueError((payload or {}).get('error', 'invalid token'))
  return payload

def _build_verify():
  # Accept a JWT access token with a file/RPC scope (public path) OR, on the
  # trusted Docker network, a static shared secret from SERVICELINK_MESH_SECRET.
  jwt = bearer_verifier(_decode_access_token)
  secret = os.getenv('SERVICELINK_MESH_SECRET')
  return any_verifier(shared_secret_verifier(secret, scopes=('mesh',)), jwt) if secret else jwt

verify = _build_verify()
link_bp = create_link_blueprint(router, verify=verify, limiter=LIMITER.limit('30 per minute'), max_body=MAX_RPC_BODY)
