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

import httpx
from quart import Blueprint, Quart, request

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

class FakeLimiter:
  def exempt(self, func):
    return func

  def limit(self, *_args, **_kwargs):
    def decorator(func):
      return func
    return decorator

class FakeLogger:
  def __init__(self):
    self.errors = []

  def error(self, message):
    self.errors.append(message)

  def warning(self, message):
    pass

class FailingConvex:
  async def get_current_favicon(self):
    raise httpx.ConnectError("[Errno -2] Name or service not known")

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_route_test_modules(monkeypatch, app, logger):
  fake_setup = types.ModuleType("my_modules.app.setup")
  fake_setup.app = app
  fake_setup.LIMITER = FakeLimiter()
  monkeypatch.setitem(sys.modules, "my_modules.app.setup", fake_setup)

  fake_constens = types.ModuleType("my_modules.app.constens")
  fake_constens.BLOCKED_IPS_ACCESSING_TIMES = 3
  fake_constens.BLOCKED_IPS_STORED_TIMEFRAME = 60
  monkeypatch.setitem(sys.modules, "my_modules.app.constens", fake_constens)

  fake_logger_module = types.ModuleType("my_modules.app.logger")
  fake_logger_module.logger = logger
  monkeypatch.setitem(sys.modules, "my_modules.app.logger", fake_logger_module)

  fake_functions = types.ModuleType("my_modules.functions")
  fake_functions.get_ip = lambda: "203.0.113.10"
  fake_functions.enforce_custom_limit = lambda *_args, **_kwargs: None
  fake_functions.get_request_context = lambda: types.SimpleNamespace(path=request.path)
  fake_functions.is_valid_uuid = lambda value: True
  monkeypatch.setitem(sys.modules, "my_modules.functions", fake_functions)

def register_template_routes(app):
  side_main = Blueprint("side_main", __name__)

  async def index():
    return "ok"

  side_main.add_url_rule("/", "index", index)
  side_main.add_url_rule("/files", "files_list", index)
  side_main.add_url_rule("/access", "access_list", index)
  app.register_blueprint(side_main)

  auth_login = Blueprint("auth_login", __name__)
  auth_login.add_url_rule("/login", "login", index)
  auth_login.add_url_rule("/logout", "logout", index)
  app.register_blueprint(auth_login)


def load_errors_and_basics(monkeypatch, app):
  logger = FakeLogger()
  install_route_test_modules(monkeypatch, app, logger)
  root = Path(__file__).resolve().parents[1]
  errors = load_module("test_routes_handeling_errorsAndBots", root / "routes" / "handeling" / "errorsAndBots.py")
  basics = load_module("test_routes_handeling_basics", root / "routes" / "handeling" / "basics.py")
  return errors, basics, logger

def test_convex_connect_error_is_returned_as_global_database_error(monkeypatch):
  async def run_test():
    app = Quart(__name__, template_folder=str(Path(__file__).resolve().parents[1] / "templates" / "side"))
    register_template_routes(app)
    app.convex = FailingConvex()
    _errors, basics, logger = load_errors_and_basics(monkeypatch, app)
    app.register_blueprint(basics.basic_bp)

    response = await app.test_client().get("/favicon.ico")

    assert response.status_code == 504
    assert any("Name or service not known" in str(error) for error in logger.errors)

  asyncio.run(run_test())

def test_api_convex_connect_error_returns_json_database_error(monkeypatch):
  async def run_test():
    app = Quart(__name__, template_folder=str(Path(__file__).resolve().parents[1] / "templates" / "side"))
    load_errors_and_basics(monkeypatch, app)

    @app.get("/api/failing")
    async def failing_api():
      raise httpx.ConnectError("[Errno -2] Name or service not known")

    response = await app.test_client().get("/api/failing")
    payload = await response.get_json()

    assert response.status_code == 504
    assert payload["error"] == "Database Error"

  asyncio.run(run_test())
