import asyncio
import importlib.util
import sys
import types
from pathlib import Path
from urllib.parse import quote

from quart import Quart, g, session

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

class _NoOpLimiter:
  def limit(self, *_args, **_kwargs):
    return lambda fn: fn

def _load_module(module_name, module_path):
  spec = importlib.util.spec_from_file_location(module_name, module_path)
  module = importlib.util.module_from_spec(spec)
  sys.modules[module_name] = module
  spec.loader.exec_module(module)
  return module

def _install_stubs(monkeypatch):
  setup_stub = types.ModuleType("my_modules.app.setup")
  setup_stub.LIMITER = _NoOpLimiter()
  monkeypatch.setitem(sys.modules, "my_modules.app.setup", setup_stub)

  header_stub = types.ModuleType("my_modules.decoratory.header")

  def login_required(func):
    async def wrapper(*args, **kwargs):
      user = session.get("user")
      if user is None:
        return "unauthorized", 401
      return await func(user=user, *args, **kwargs)
    wrapper.__name__ = func.__name__
    return wrapper

  header_stub.login_required = login_required
  monkeypatch.setitem(sys.modules, "my_modules.decoratory.header", header_stub)

def _client(monkeypatch):
  _install_stubs(monkeypatch)
  root = Path(__file__).resolve().parents[1]
  module = _load_module("link_composer_route_under_test", root / "routes" / "side" / "link_composer.py")
  app = Quart(__name__, template_folder=str(root / "templates" / "side"))
  app.secret_key = "test-secret"
  app.register_blueprint(module.link_composer_bp)

  @app.before_request
  async def wide_event_context():
    g.wide_event = {}

  return app, app.test_client()

def test_decode_api_is_public(monkeypatch):
  async def run_test():
    _app, client = _client(monkeypatch)
    from link_composer.codec import encode_url

    code = encode_url("https://example.com/target").code
    response = await client.post("/api/links/decode", json={"code": code})
    payload = await response.get_json()

    assert response.status_code == 200
    assert payload["ok"] is True
    assert payload["url"] == "https://example.com/target"

  asyncio.run(run_test())

def test_decode_api_accepts_redirect_url(monkeypatch):
  async def run_test():
    _app, client = _client(monkeypatch)
    from link_composer.codec import encode_url

    code = encode_url("https://example.com/target").code
    response = await client.post("/api/links/decode", json={"code": f"http://localhost/l/{code}"})
    payload = await response.get_json()

    assert response.status_code == 200
    assert payload["ok"] is True
    assert payload["url"] == "https://example.com/target"

  asyncio.run(run_test())

def test_encode_api_requires_login(monkeypatch):
  async def run_test():
    _app, client = _client(monkeypatch)

    response = await client.post("/api/links/encode", json={"url": "https://example.com/target"})

    assert response.status_code == 401

  asyncio.run(run_test())

def test_encode_api_returns_qr_for_logged_in_user(monkeypatch):
  async def run_test():
    _app, client = _client(monkeypatch)
    async with client.session_transaction() as sess:
      sess["user"] = {"sub": "user_123", "name": "Demo"}

    response = await client.post("/api/links/encode", json={"url": "https://example.com/target"})
    payload = await response.get_json()

    assert response.status_code == 200
    assert payload["ok"] is True
    assert payload["code"].startswith("u")
    encoded_code = quote(payload["code"], safe="")
    assert payload["redirect_url"].endswith(f"/l/{encoded_code}")
    assert f"/links/qr/{encoded_code}" in payload["qr_url"]

  asyncio.run(run_test())

def test_qr_endpoint_returns_png_after_decoding(monkeypatch):
  async def run_test():
    _app, client = _client(monkeypatch)
    from link_composer.codec import encode_url

    code = encode_url("https://example.com/target").code
    response = await client.get(f"/links/qr/{code}")

    assert response.status_code == 200
    assert response.headers["Content-Type"] == "image/png"
    assert (await response.get_data()).startswith(b"\x89PNG")

  asyncio.run(run_test())
