"""
This file originates in shared/shared_url.py, and
is copied verbatim into the different projects. It should
not be edited directly, edit the shared version and replicate
it everywhere.
"""

from __future__ import annotations

from typing import Any
from urllib.parse import urlparse


class URLError(ValueError):
    """Raised when URL.parse() receives an invalid or conflicting input."""


class URL:
    """Parsed URL value object.

    Accepts a bare hostname or a full URL and normalises it so that
    ``str(URL.parse(value))`` is always safe to use in f-strings.

    Not a dataclass — deliberately a plain class so that pydantic-settings
    treats it as a scalar type (``is_dataclass()`` returns False) and reads
    the env-var as a plain string instead of trying to JSON-parse it.

    >>> str(URL.parse("broker.example.com"))
    'https://broker.example.com'
    >>> str(URL.parse("http://broker.internal:8080"))
    'http://broker.internal:8080'
    """

    __slots__ = ("url", "netloc", "port", "scheme")

    def __init__(self, *, url: str, netloc: str, port: int, scheme: str) -> None:
        object.__setattr__(self, "url", url)
        object.__setattr__(self, "netloc", netloc)
        object.__setattr__(self, "port", port)
        object.__setattr__(self, "scheme", scheme)

    def __setattr__(self, name: str, value: Any) -> None:
        raise AttributeError("URL instances are immutable")

    def __delattr__(self, name: str) -> None:
        raise AttributeError("URL instances are immutable")

    def __eq__(self, other: object) -> bool:
        if not isinstance(other, URL):
            return NotImplemented
        return (self.url, self.netloc, self.port, self.scheme) == (
            other.url,
            other.netloc,
            other.port,
            other.scheme,
        )

    def __hash__(self) -> int:
        return hash((self.url, self.netloc, self.port, self.scheme))

    def __repr__(self) -> str:
        return (
            f"URL(url={self.url!r}, netloc={self.netloc!r},"
            f" port={self.port!r}, scheme={self.scheme!r})"
        )

    @classmethod
    def __get_pydantic_core_schema__(cls, _source_type: type, _handler: Any) -> Any:
        """Tell Pydantic to accept a plain string and run URL.parse()."""
        from pydantic_core import core_schema

        def _validate(value: Any) -> URL:
            if isinstance(value, URL):
                return value
            return URL.parse(str(value))

        return core_schema.no_info_plain_validator_function(
            _validate,
            serialization=core_schema.to_string_ser_schema(),
        )

    @classmethod
    def parse(
        cls,
        host_or_url: str,
        scheme: str = "https",
        port: int | None = None,
    ) -> URL:
        """Parse a host or URL string into a URL object.

        Handles bare hostnames (prepends *scheme*://), double-protocol
        prefixes (``https://https://…``), and leading/trailing whitespace
        or slashes.

        If *port* is given and the input string already contains an
        explicit port, ``ValueError`` is raised to flag the conflict.
        Pass the port either in the string or via the parameter, not
        both.
        """
        host_or_url = host_or_url.strip().strip("/")
        # Fix double protocol if it happens
        if host_or_url.startswith("https://https://"):
            host_or_url = host_or_url.replace("https://", "", 1)
        if host_or_url.startswith("http://http://"):
            host_or_url = host_or_url.replace("http://", "", 1)
        if not host_or_url.startswith("http"):
            host_or_url = f"{scheme}://{host_or_url}"
        parsed = urlparse(host_or_url)
        if port is not None and parsed.port is not None:
            raise URLError(
                f"port conflict: {host_or_url!r} already contains port"
                f" {parsed.port}, but port={port} was also passed"
            )
        if port is not None:
            # Rebuild netloc with the requested port
            hostname = parsed.hostname or ""
            netloc = f"{hostname}:{port}"
            parsed = parsed._replace(netloc=netloc)
        resolved_port = (
            port
            if port is not None
            else parsed.port
            if parsed.port is not None
            else cls._default_port_from_scheme(parsed.scheme)
        )
        return cls(
            url=parsed.geturl(),
            netloc=parsed.netloc,
            port=resolved_port,
            scheme=parsed.scheme,
        )

    @classmethod
    def _default_port_from_scheme(cls, scheme: str) -> int:
        if scheme == "https":
            return 443
        elif scheme == "http":
            return 80
        else:
            raise URLError(f"unsupported url scheme: {scheme}")

    def __format__(self, _format_spec: str) -> str:
        return self.url

    def __str__(self) -> str:
        return self.url

    def __truediv__(self, sub_path: str) -> URL:
        return URL(
            url=f"{self.url}/{sub_path.strip('/')}",
            netloc=self.netloc,
            port=self.port,
            scheme=self.scheme,
        )

    def with_userinfo(self, username: str, password: str = "") -> URL:
        """Return a new URL with the given userinfo (``user:pass@``) embedded."""
        parsed = urlparse(self.url)
        netloc = (
            f"{username}:{password}@{parsed.hostname}"
            if password
            else f"{username}@{parsed.hostname}"
        )
        if parsed.port:
            netloc = f"{netloc}:{parsed.port}"
        new_url = parsed._replace(netloc=netloc).geturl()
        return URL(url=new_url, netloc=self.netloc, port=self.port, scheme=self.scheme)
