import asyncio
import importlib.util
import json
import sys
import types
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parents[1]))

from quart import Quart

from servicelink import Request, handle_envelope

# Load routes/api/link.py in isolation. Importing it as `routes.api.link` would
# run routes/__init__.py -> my_modules.app.setup -> constens (asyncio.run at
# import), which fails under pytest's output capture. We stub my_modules.app.setup
# with a no-op limiter; link.py's other deps (my_modules.expiry/file_meta,
# servicelink, quart) are side-effect free.
class _NoOpLimiter:
  def limit(self, *args, **kwargs):
    return lambda fn: fn

_setup_stub = types.ModuleType('my_modules.app.setup')
_setup_stub.LIMITER = _NoOpLimiter()
sys.modules.setdefault('my_modules.app.setup', _setup_stub)

_LINK_PATH = Path(__file__).resolve().parents[1] / 'routes' / 'api' / 'link.py'
_spec = importlib.util.spec_from_file_location('picoshare_link_under_test', _LINK_PATH)
link = importlib.util.module_from_spec(_spec)
_spec.loader.exec_module(link)

class FakeConvex:
  def __init__(self):
    self.files = {}

  async def decode_access_token_payload(self, access_token, required_scope=None, required_scopes=None):
    if access_token == 'good':
      return {'sub': 'user_123', 'scope': 'all'}
    if access_token == 'nomesh':
      return {'sub': 'user_123', 'scope': 'files'}
    return {'error': 'invalid token'}

  async def send_to_storage(self, data, content_type):
    return 'storage_1'

  async def add_file(self, file_name, file_size, note, content_type, expires_at, storage_id, user_id, file_path=''):
    self.files[file_name] = {
      'file_name': file_name,
      'file_path': file_path,
      'note': note,
      'content_type': content_type,
      'storage_id': storage_id,
      'user_id': user_id,
    }
    return self.files[file_name]

  async def get_file(self, file_id):
    return self.files.get(file_id)

  async def get_files(self, user_id):
    return list(self.files.values())

def _call(envelope, token=None):
  # Call the handler the /rpc route delegates to, bypassing the LIMITER/size-cap
  # wrapper; exercises the real verify (mesh scope) + dispatch + handlers.
  async def run():
    app = Quart(__name__)
    app.convex = FakeConvex()
    async with app.test_request_context('/rpc', method='POST'):
      status, body, _ = await handle_envelope(
        link.router,
        json.dumps(envelope).encode('utf-8'),
        authorization=f'Bearer {token}' if token else None,
        verify=link.verify,
        content_type='application/json',
      )
    return status, json.loads(body)

  return asyncio.run(run())

def _request(method, params=None):
  return Request.create(method, params or {}, source='browser-cli', target='picoshare').to_dict()

def test_text_upload_roundtrip():
  status, body = _call(_request('files.upload', {'text': 'hello mesh', 'file_name': 'note.txt', 'note': 'scraped'}), token='good')
  assert status == 200
  assert body['ok'] is True
  assert body['result']['file_name'] == 'note.txt'
  assert body['result']['content_type'] == 'text/plain'
  assert body['result']['size'] == len('hello mesh')

def test_upload_requires_content():
  status, body = _call(_request('files.upload', {'file_name': 'x'}), token='good')
  assert status == 422
  assert body['error']['code'] == 'invalid_params'

def test_get_missing_file_is_not_found():
  status, body = _call(_request('files.get', {'file_id': 'ghost'}), token='good')
  assert status == 404
  assert body['error']['code'] == 'not_found'

def test_missing_token_is_unauthorized():
  status, body = _call(_request('files.list'))
  assert status == 401
  assert body['error']['code'] == 'unauthorized'

def test_token_with_files_scope_is_allowed():
  status, body = _call(_request('files.list'), token='nomesh')
  assert status == 200
  assert body['ok'] is True
