"""
Classes used by the LocalServerLoginFlowManager to automatically receive an
auth code from a redirect after user authentication through a locally run web-server.

These classes generally shouldn't need to be used directly.
"""

from __future__ import annotations

import importlib.resources
import os
import queue
import socket
import sys
import time
import typing as t
from datetime import timedelta
from http.server import BaseHTTPRequestHandler, HTTPServer
from string import Template
from urllib.parse import parse_qsl, urlparse

from . import html_files
from .errors import LocalServerLoginError

_IS_WINDOWS = os.name == "nt"

DEFAULT_HTML_TEMPLATE = Template(
    importlib.resources.files(html_files)
    .joinpath("local_server_landing_page.html")
    .read_text()
)


class RedirectHandler(BaseHTTPRequestHandler):
    """
    BaseHTTPRequestHandler to be used by RedirectHTTPServer.
    Displays the RedirectHTTPServer's html_template and parses auth_code out of
    the redirect url.
    """

    server: RedirectHTTPServer

    def do_GET(self) -> None:
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.end_headers()

        html_template = self.server.html_template
        query_params = dict(parse_qsl(urlparse(self.path).query))
        code = query_params.get("code")
        if code:
            self.wfile.write(
                html_template.substitute(
                    post_login_message="", login_result="Login successful"
                ).encode("utf-8")
            )
            self.server.return_code(code)
        else:
            msg = query_params.get("error_description", query_params.get("error"))

            self.wfile.write(
                html_template.substitute(
                    post_login_message=msg, login_result="Login failed"
                ).encode("utf-8")
            )

            self.server.return_code(LocalServerLoginError(msg))


class RedirectHTTPServer(HTTPServer):
    """
    An HTTPServer which accepts an HTML `Template` to be displayed to the user
    and uses a Queue to receive an auth_code from its RequestHandler.
    """

    WAIT_TIMEOUT = timedelta(minutes=15)

    def __init__(
        self,
        server_address: tuple[str, int],
        handler_class: type[BaseHTTPRequestHandler],
        html_template: Template,
    ) -> None:
        super().__init__(server_address, handler_class)

        self.html_template = html_template
        self._auth_code_queue: queue.Queue[str | BaseException] = queue.Queue()

    def handle_error(
        self,
        request: socket.socket | tuple[bytes, socket.socket],
        client_address: t.Any,
    ) -> None:
        _, excval, _ = sys.exc_info()
        assert excval is not None
        self._auth_code_queue.put(excval)

    def return_code(self, code: str | BaseException) -> None:
        self._auth_code_queue.put_nowait(code)

    def wait_for_code(self) -> str | BaseException:
        # Windows needs special handling as blocking prevents ctrl-c interrupts
        if _IS_WINDOWS:
            deadline = time.time() + self.WAIT_TIMEOUT.total_seconds()
            while time.time() < deadline:
                try:
                    return self._auth_code_queue.get()
                except queue.Empty:
                    time.sleep(1)
        else:
            try:
                return self._auth_code_queue.get(block=True, timeout=3600)
            except queue.Empty:
                pass
        raise LocalServerLoginError("Login timed out. Please try again.")
