import re
import subprocess
from _typeshed import StrPath
from builtins import range as _range
from collections import OrderedDict
from collections.abc import Callable, Generator, Iterable, Iterator
from datetime import datetime
from logging import Logger
from types import TracebackType
from typing import Any, Final, Literal, Protocol, SupportsIndex, TypeVar, overload, type_check_only
from typing_extensions import Self, TypeAlias

from croniter.croniter import croniter
from cronlog import CronLog

_User: TypeAlias = str | bool | None
_K = TypeVar("_K")
_V = TypeVar("_V")

# cron_descriptor.Options class
@type_check_only
class _Options(Protocol):
    casing_type: Literal[1, 2, 3]
    verbose: bool
    day_of_week_start_index_zero: bool
    use_24hour_time_format: bool
    locale_location: StrPath | None
    locale_code: str | None
    def __init__(self) -> None: ...

__pkgname__: Final[str]
__version__: Final[str]
ITEMREX: Final[re.Pattern[str]]
SPECREX: Final[re.Pattern[str]]
DEVNULL: Final[str]
WEEK_ENUM: Final[list[str]]
MONTH_ENUM: Final[list[str | None]]
SPECIALS_CONVERSION: Final[bool]
SPECIALS: Final[dict[str, str]]
SPECIAL_IGNORE: Final[list[str]]
S_INFO: Final[list[dict[str, str | int | list[str] | list[str | None]]]]
WINOS: Final[bool]
POSIX: Final[bool]
SYSTEMV: Final[bool]
ZERO_PAD: Final[bool]
LOG: Logger
CRON_COMMAND: Final[str]
SHELL: Final[str]
current_user: Callable[[], str | None]

class Process:
    env: subprocess._ENV | None
    args: tuple[str, ...]
    has_run: bool
    stdout: str | None
    stderr: str | None
    returncode: int | None
    # `posix` and `env` are known special kwargs:
    def __init__(self, cmd: str, *args: str, posix: bool = ..., env: subprocess._ENV | None = None, **flags: object) -> None: ...
    def run(self) -> Self: ...
    def __int__(self) -> int: ...  # technically, it can return `None` before `run` is called
    def __eq__(self, other: object) -> bool: ...

class CronTab:
    lines: list[str | CronItem] | None
    crons: list[CronItem] | None
    filen: str | None
    cron_command: str
    env: OrderedVariableList[str, str] | None
    root: bool
    intab: str | None
    tabfile: str | None
    def __init__(
        self, user: _User = ..., tab: str | None = ..., tabfile: str | None = ..., log: CronLog | str | None = ...
    ) -> None: ...
    def __enter__(self) -> Self: ...
    def __exit__(
        self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None
    ) -> None: ...
    @property
    def log(self) -> CronLog: ...
    @property
    def user(self) -> _User: ...
    @property
    def user_opt(self) -> dict[str, str]: ...
    def read(self, filename: str | None = ...) -> None: ...
    def append(
        self,
        item: CronItem,
        line: str = ...,
        read: bool = ...,
        before: str | re.Pattern[str] | list[CronItem] | tuple[CronItem, ...] | Generator[CronItem] | None = ...,
    ) -> None: ...
    def write(self, filename: str | None = ..., user: _User = ..., errors: bool = ...) -> None: ...
    def write_to_user(self, user: bool | str = ...) -> None: ...
    # Usually `kwargs` are just `now: datetime | None`, but technically this can
    # work for `CronItem` subclasses, which might define other kwargs.
    def run_pending(self, *, now: datetime | None = ..., **kwargs: Any) -> Iterator[str]: ...
    def run_scheduler(self, timeout: int = -1, cadence: int = 60, warp: bool = False) -> Iterator[str]: ...
    def render(self, errors: bool = ...) -> str: ...
    def new(
        self,
        command: str = ...,
        comment: str = ...,
        user: str | None = ...,
        pre_comment: bool = ...,
        before: str | re.Pattern[str] | list[CronItem] | tuple[CronItem, ...] | Generator[CronItem] | None = ...,
    ) -> CronItem: ...
    def find_command(self, command: str | re.Pattern[str]) -> Iterator[CronItem]: ...
    def find_comment(self, comment: str | re.Pattern[str]) -> Iterator[CronItem]: ...
    def find_time(self, *args: Any) -> Iterator[CronItem]: ...
    @property
    def commands(self) -> Iterator[str]: ...
    @property
    def comments(self) -> Iterator[str]: ...
    # You cannot actually pass `*args`, it will raise an exception,
    # also known kwargs are added:
    def remove_all(
        self, *, command: str | re.Pattern[str] = ..., comment: str | re.Pattern[str] = ..., time: Any = ..., **kwargs: object
    ) -> int: ...
    def remove(self, *items: CronItem | Iterable[CronItem]) -> int: ...
    def __iter__(self) -> Iterator[CronItem]: ...
    def __getitem__(self, i: SupportsIndex) -> CronItem: ...
    def __len__(self) -> int: ...

class CronItem:
    cron: CronTab | None
    user: _User
    valid: bool
    enabled: bool
    special: bool
    comment: str
    command: str | None
    last_run: datetime | None
    env: OrderedVariableList[str, str]
    pre_comment: bool
    marker: str | None
    stdin: str | None
    slices: CronSlices
    def __init__(self, command: str = ..., comment: str = ..., user: _User = ..., pre_comment: bool = ...) -> None: ...
    def __hash__(self) -> int: ...
    def __eq__(self, other: object) -> bool: ...
    @classmethod
    def from_line(cls, line: str, user: str | None = ..., cron: CronTab | None = ...) -> Self: ...
    def delete(self) -> None: ...
    def set_command(self, cmd: str, parse_stdin: bool = ...) -> None: ...
    def set_comment(self, cmt: str, pre_comment: bool = ...) -> None: ...
    def parse(self, line: str) -> None: ...
    def enable(self, enabled: bool = ...) -> bool: ...
    def is_enabled(self) -> bool: ...
    def is_valid(self) -> bool: ...
    def render(self) -> str: ...
    def every_reboot(self) -> None: ...
    def every(self, unit: int = ...) -> Every: ...
    def setall(self, *args: Any) -> None: ...
    def clear(self) -> None: ...
    def frequency(self, year: int | None = ...) -> int: ...
    def frequency_per_year(self, year: int | None = ...) -> int: ...
    def frequency_per_day(self) -> int: ...
    def frequency_per_hour(self) -> int: ...
    def frequency_at_year(self, year: int | None = None) -> int: ...
    @overload
    def frequency_at_month(self, year: int, month: int) -> int: ...
    @overload
    def frequency_at_month(self, year: None = None, month: None = None) -> int: ...
    @overload
    def frequency_at_day(self, year: int, month: int, day: int) -> int: ...
    @overload
    def frequency_at_day(self, year: None = None, month: None = None, day: None = None) -> int: ...
    @overload
    def frequency_at_hour(self, year: int, month: int, day: int, hour: int) -> int: ...
    @overload
    def frequency_at_hour(self, year: None = None, month: None = None, day: None = None, hour: None = None) -> int: ...
    def run_pending(self, now: datetime | None = ...) -> int | str: ...
    def run(self) -> str: ...
    def schedule(self, date_from: datetime | None = ...) -> croniter: ...
    def description(
        self,
        *,
        options: _Options | None = None,
        casing_type: Literal[1, 2, 3] = 2,
        verbose: bool = False,
        day_of_week_start_index_zero: bool = True,
        use_24hour_time_format: bool = ...,
        locale_location: StrPath | None = None,
        locale_code: str | None = ...,
    ) -> str | None: ...
    @property
    def log(self) -> CronLog: ...
    @property
    def minute(self) -> int | str: ...
    @property
    def minutes(self) -> int | str: ...
    @property
    def hour(self) -> int | str: ...
    @property
    def hours(self) -> int | str: ...
    @property
    def day(self) -> int | str: ...
    @property
    def dom(self) -> int | str: ...
    @property
    def month(self) -> int | str: ...
    @property
    def months(self) -> int | str: ...
    @property
    def dow(self) -> int | str: ...
    def __len__(self) -> int: ...
    def __getitem__(self, key: int | str) -> int | str: ...
    def __lt__(self, value: object) -> bool: ...
    def __gt__(self, value: object) -> bool: ...

class Every:
    slices: CronSlices
    unit: int
    # TODO: add generated attributes
    def __init__(self, item: CronSlices, units: int) -> None: ...
    def set_attr(self, target: int) -> Callable[[], None]: ...
    def year(self) -> None: ...

class CronSlices(list[CronSlice]):
    special: bool | None
    def __init__(self, *args: Any) -> None: ...
    def is_self_valid(self, *args: Any) -> bool: ...
    @classmethod
    def is_valid(cls, *args: Any) -> bool: ...
    def setall(self, *slices: str) -> None: ...
    def clean_render(self) -> str: ...
    def render(self) -> str: ...
    def clear(self) -> None: ...
    def frequency(self, year: int | None = ...) -> int: ...
    def frequency_per_year(self, year: int | None = ...) -> int: ...
    def frequency_per_day(self) -> int: ...
    def frequency_per_hour(self) -> int: ...
    def frequency_at_year(self, year: int | None = None) -> int: ...
    @overload
    def frequency_at_month(self, year: int, month: int) -> int: ...
    @overload
    def frequency_at_month(self, year: None = None, month: None = None) -> int: ...
    @overload
    def frequency_at_day(self, year: int, month: int, day: int) -> int: ...
    @overload
    def frequency_at_day(self, year: None = None, month: None = None, day: None = None) -> int: ...
    @overload
    def frequency_at_hour(self, year: int, month: int, day: int, hour: int) -> int: ...
    @overload
    def frequency_at_hour(self, year: None = None, month: None = None, day: None = None, hour: None = None) -> int: ...
    def __eq__(self, arg: object) -> bool: ...

class SundayError(KeyError): ...

class Also:
    obj: CronSlice
    def __init__(self, obj: CronSlice) -> None: ...
    # These method actually use `*args`, but pass them to `CronSlice` methods,
    # this is why they are typed as `Any`.
    def every(self, *a: Any) -> _Part: ...
    def on(self, *a: Any) -> list[_Part]: ...
    def during(self, *a: Any) -> _Part: ...

_Part: TypeAlias = int | CronValue | CronRange

class CronSlice:
    min: int | None
    max: int | None
    name: str | None
    enum: list[str | None] | None
    parts: list[_Part]
    def __init__(self, info: int | dict[str, Any], value: str | None = ...) -> None: ...
    def __hash__(self) -> int: ...
    def parse(self, value: str | None) -> None: ...
    def render(self, resolve: bool = False) -> str: ...
    def __eq__(self, arg: object) -> bool: ...
    def every(self, n_value: int, also: bool = ...) -> _Part: ...
    # The only known kwarg, others are unused,
    # `*args`` are passed to `parse_value`, so they are `Any`
    def on(self, *n_value: Any, also: bool = ...) -> list[_Part]: ...
    def during(self, vfrom: int | str, vto: int | str, also: bool = ...) -> _Part: ...
    @property
    def also(self) -> Also: ...
    def clear(self) -> None: ...
    def get_range(self, *vrange: int | str | CronValue) -> list[int | CronRange]: ...
    def __iter__(self) -> Iterator[int]: ...
    def __len__(self) -> int: ...
    def parse_value(self, val: str, sunday: int | None = ...) -> int | CronValue: ...
    def test_value(self, value: str, sunday: int | None = None) -> str: ...

def get_cronvalue(value: int, enums: list[str]) -> int | CronValue: ...

class CronValue:
    text: str
    value: int
    def __init__(self, value: str, enums: list[str]) -> None: ...
    def __lt__(self, value: object) -> bool: ...
    def __int__(self) -> int: ...

class CronRange:
    dangling: int | None
    slice: str
    cron: CronTab | None
    seq: int
    def __init__(self, vslice: str, *vrange: int | str | CronValue) -> None: ...
    # Are not set in `__init__`:
    vfrom: int | CronValue
    vto: int | CronValue
    def parse(self, value: str) -> None: ...
    def all(self) -> None: ...
    def render(self, resolve: bool = ...) -> str: ...
    def range(self) -> _range: ...
    def every(self, value: int | str) -> None: ...
    def __lt__(self, value: object) -> bool: ...
    def __gt__(self, value: object) -> bool: ...
    def __int__(self) -> int: ...

class OrderedVariableList(OrderedDict[_K, _V]):
    job: CronItem | None
    # You cannot actually pass `*args`, it will raise an exception,
    # also known kwargs are added:
    def __init__(self, *, job: CronItem | None = None, **kw: _V) -> None: ...
    @property
    def previous(self) -> Self | None: ...
    def all(self) -> Self: ...
    def __getitem__(self, key: _K) -> _V: ...
