from __future__ import annotations

from urllib.parse import quote, unquote, urlparse

from quart import Blueprint, Response, abort, jsonify, render_template, request, session, url_for

from link_composer.codec import decode_url, encode_url
from link_composer.constants import ALLOWED_REDIRECT_SCHEMES
from link_composer.qr import make_qr_png
from link_composer.security import validate_redirect_url
from my_modules.app.setup import LIMITER
from my_modules.decoratory.header import login_required
from quart_common.web.wide_event import add_wide_event_context

link_composer_bp = Blueprint("link_composer", __name__)

LINK_REDIRECT_PREFIX = "/l/"
LINK_QR_PREFIX = "/links/qr/"

def _external_url(endpoint: str, **values: str) -> str:
  return request.url_root.rstrip("/") + url_for(endpoint, **values)

def _external_code_url(prefix: str, code: str) -> str:
  return request.url_root.rstrip("/") + prefix + quote(code, safe="")

def _extract_code(value: str) -> str:
  raw_value = value.strip()
  parsed = urlparse(raw_value)

  if parsed.scheme in ALLOWED_REDIRECT_SCHEMES:
    path = parsed.path + (f";{parsed.params}" if parsed.params else "")
    if path.startswith(LINK_REDIRECT_PREFIX):
      return unquote(path.removeprefix(LINK_REDIRECT_PREFIX))
    if path.startswith(LINK_QR_PREFIX):
      return unquote(path.removeprefix(LINK_QR_PREFIX))

  return raw_value

def _decode_or_400(code: str) -> str:
  try:
    return validate_redirect_url(decode_url(_extract_code(code)))
  except ValueError:
    abort(400)

@link_composer_bp.get("/links")
@LIMITER.limit("30 per minute;500 per hour;")
async def link_composer_page():
  add_wide_event_context(nanoshare={"operation": "link_composer_page"})
  return await render_template(
    "views/webpage/links/composer.htm",
    can_encode=session.get("user") is not None,
    encode_api_url=url_for("link_composer.encode_link_api"),
    decode_api_url=url_for("link_composer.decode_link_api"),
    redirect_prefix=LINK_REDIRECT_PREFIX,
    qr_prefix=LINK_QR_PREFIX,
  )

@link_composer_bp.post("/api/links/encode")
@LIMITER.limit("30 per minute;500 per hour;")
@login_required
async def encode_link_api(user):
  add_wide_event_context(nanoshare={"operation": "link_encode", "user_id": user.get("sub")})
  payload = await request.get_json(silent=True) or {}
  url = str(payload.get("url") or "").strip()
  qr_mode = bool(payload.get("qr"))

  try:
    result = encode_url(url, qr=qr_mode)
    decoded_url = validate_redirect_url(decode_url(result.code))
  except ValueError as exc:
    return jsonify({"ok": False, "error": str(exc)}), 400

  redirect_url = _external_code_url(LINK_REDIRECT_PREFIX, result.code)
  qr_url = _external_code_url(LINK_QR_PREFIX, result.code)

  return jsonify({
    "ok": True,
    "code": result.code,
    "url": decoded_url,
    "redirect_url": redirect_url,
    "qr_url": qr_url,
    "original_length": result.original_length,
    "code_length": result.code_length,
    "bit_length": result.bit_length,
    "mode": result.mode,
    "ratio": round(result.code_length / result.original_length, 3),
  })

@link_composer_bp.post("/api/links/decode")
@LIMITER.limit("60 per minute;1000 per hour;")
async def decode_link_api():
  add_wide_event_context(nanoshare={"operation": "link_decode"})
  payload = await request.get_json(silent=True) or {}
  code = _extract_code(str(payload.get("code") or ""))

  try:
    url = validate_redirect_url(decode_url(code))
  except ValueError as exc:
    return jsonify({"ok": False, "error": str(exc)}), 400

  return jsonify({"ok": True, "url": url})

@link_composer_bp.get(f"{LINK_REDIRECT_PREFIX}<path:code>")
@LIMITER.limit("120 per minute;2000 per hour;")
async def redirect_link(code: str):
  add_wide_event_context(nanoshare={"operation": "link_redirect"})
  url = _decode_or_400(code)
  return Response(status=307, headers={"Location": url, "Cache-Control": "no-store"})

@link_composer_bp.get(f"{LINK_QR_PREFIX}<path:code>")
@LIMITER.limit("60 per minute;1000 per hour;")
async def qr_link(code: str):
  add_wide_event_context(nanoshare={"operation": "link_qr"})
  _decode_or_400(code)
  redirect_url = _external_code_url(LINK_REDIRECT_PREFIX, code)
  return Response(make_qr_png(redirect_url), content_type="image/png")
