# -*- coding: utf-8 -*-
from __future__ import annotations

import pathlib
import typing
import urllib.parse


class URL:
    """
    The URL class.

    Example:

    .. code-block:: python

        url = URL('https://ziomek.dog/').\
            set_path('/index_old.html').\
            set_query({
                'test': 'true',
            }).\
            to_string()

    The *encoding* argument will be used to decode *initial* if it's
    ```bytes``. Also, it'll be used when decoding QS.
    """
    def __init__(self,
                 initial: str | bytes | pathlib.Path | urllib.parse.ParseResult,
                 encoding: str = 'utf-8'):
        self.encoding = encoding
        self.parsed_url: urllib.parse.ParseResult

        if isinstance(initial, urllib.parse.ParseResult):
            self.parsed_url = initial
        elif isinstance(initial, pathlib.Path):
            self.parsed_url = urllib.parse.urlparse(str(initial))
        elif isinstance(initial, bytes):
            self.parsed_url = urllib.parse.urlparse(
                initial.decode(self.encoding),
            )
        else:
            self.parsed_url = urllib.parse.urlparse(initial)

    def replace_parsed_url_field(self, field: str, value: typing.Any):
        self.parsed_url = self.parsed_url._replace(**{field: value})

        return self

    def append_path_component(self, component: str | pathlib.Path) -> URL:
        _ = self.set_path(self.path / component)

        return self

    @property
    def scheme(self) -> str:
        return self.parsed_url.scheme

    def set_scheme(self, new_scheme: str) -> URL:
        self.replace_parsed_url_field('scheme', new_scheme)

        return self

    @property
    def netloc(self) -> str:
        return self.parsed_url.netloc

    def set_netloc(self, new_netloc: str) -> URL:
        self.replace_parsed_url_field('netloc', new_netloc)

        return self

    @property
    def hostname(self) -> str | None:
        return self.parsed_url.hostname

    @property
    def port(self) -> int | None:
        return self.parsed_url.port

    @property
    def username(self) -> str | None:
        return self.parsed_url.username

    @property
    def password(self) -> str | None:
        return self.parsed_url.password

    @property
    def raw_path(self) -> str:
        return self.parsed_url.path

    @property
    def path(self) -> pathlib.PurePosixPath:
        return pathlib.PurePosixPath(self.parsed_url.path)

    def set_path(self, new_path: str | pathlib.Path | pathlib.PurePath) -> URL:
        self.replace_parsed_url_field(
            'path', str(pathlib.PurePosixPath(new_path)),
        )

        return self

    @property
    def params(self) -> str:
        return self.parsed_url.params

    def set_params(self, new_params: str) -> URL:
        self.replace_parsed_url_field('params', new_params)

        return self

    @property
    def query(self) -> dict[typing.AnyStr, list[typing.AnyStr]]:
        if self.parsed_url.query == '':
            return {}

        return urllib.parse.parse_qs(
            self.parsed_url.query, encoding=self.encoding,  # type: ignore[arg-type]
        )

    def set_query(self,
                  new_query: dict[typing.AnyStr, list[typing.AnyStr]] | list,
                  ) -> URL:
        self.replace_parsed_url_field(
            'query', urllib.parse.urlencode(new_query, doseq=True),
        )

        return self

    @property
    def query_string(self) -> str:
        return self.parsed_url.query

    def set_query_string(self, new_query_string: str) -> URL:
        self.replace_parsed_url_field('query', new_query_string)

        return self

    @property
    def fragment(self) -> str:
        return self.parsed_url.fragment

    def set_fragment(self, new_fragment: str) -> URL:
        self.replace_parsed_url_field('fragment', new_fragment)

        return self

    def to_string(self) -> str:
        return urllib.parse.urlunparse(self.parsed_url)

    def __truediv__(self, component: str | pathlib.Path) -> URL:
        return self.append_path_component(component)

    def __mod__(self,
                query: dict[typing.AnyStr, list[typing.AnyStr]],
                ) -> URL:
        return self.set_query(
            {**self.query, **query},
        )

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

    def __repr__(self) -> str:
        return f'<URL {self}>'

    def __bytes__(self) -> bytes:
        return self.__str__().encode(self.encoding)
