#!/usr/bin/env python


import os
import signal
from time import sleep
from random import randrange
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo
from pathlib import Path
from bandcampsync import version, logger, do_sync, BandcampSyncOptions


def parse_bool(value):
    if isinstance(value, bool):
        return value
    return str(value).strip().lower() in {"1", "true", "yes", "on"}


def parse_until_date(value):
    value = value.strip()
    if not value:
        return None
    try:
        return datetime.strptime(value, "%Y-%m-%d").date()
    except ValueError as e:
        raise ValueError(f"Invalid UNTIL_DATE: {value}") from e


log = logger.get_logger("service")


class CatchShutdownSignal:
    def __init__(self):
        self.shutdown = False
        signal.signal(signal.SIGINT, self.got_exit_signal)
        signal.signal(signal.SIGTERM, self.got_exit_signal)

    def got_exit_signal(self, *args, **kwargs):
        self.shutdown = True


if __name__ == "__main__":
    tz_name = os.getenv("TZ", "UTC")
    cookies_path_env = os.getenv("COOKIES_FILE", "/config/cookies.txt")
    ign_file_path_env = os.getenv("IGNORES_FILE", "/config/ignores.txt")
    dir_path_env = os.getenv("DIRECTORY", "/downloads")
    media_format_env = os.getenv("FORMAT", "flac")
    ign_patterns = os.getenv("IGNORE", "")
    run_daily_at_env = os.getenv("RUN_DAILY_AT", "3")
    exit_after_run_env = os.getenv("EXIT_AFTER_RUN", "0")
    temp_dir_env = os.getenv("TEMP_DIR", "")
    notify_url_env = os.getenv("NOTIFY_URL", "")
    until_date = parse_until_date(os.getenv("UNTIL_DATE", ""))
    dry_run = parse_bool(os.getenv("DRY_RUN", "0"))
    max_retries_env = os.getenv("MAX_RETRIES", "3")
    retry_wait_env = os.getenv("RETRY_WAIT", "5")
    concurrency_env = os.getenv("CONCURRENCY", "1")
    skip_item_index_env = os.getenv("SKIP_ITEM_INDEX", "0")
    sync_ignore_file_env = os.getenv("SYNC_IGNORE_FILE", "0")
    skip_hidden_env = os.getenv("SKIP_HIDDEN", "0")

    try:
        max_retries = int(max_retries_env)
    except (ValueError, TypeError):
        max_retries = 3
    try:
        retry_wait = int(retry_wait_env)
    except (ValueError, TypeError):
        retry_wait = 5
    try:
        concurrency = int(concurrency_env)
    except (ValueError, TypeError):
        concurrency = 1
    skip_item_index = parse_bool(skip_item_index_env)
    sync_ignore_file = parse_bool(sync_ignore_file_env)
    skip_hidden = parse_bool(skip_hidden_env)

    cookies_path = Path(cookies_path_env).resolve()
    if not cookies_path.is_file():
        raise ValueError(f"Cookies file does not exist: {cookies_path}")
    with open(cookies_path, "rt") as f:
        cookies = f.read().strip()
    log.info(f'Loaded cookies from "{cookies_path}"')

    ign_file_path = Path(ign_file_path_env).resolve()
    dir_path = Path(dir_path_env).resolve()
    if skip_item_index and not ign_file_path:
        raise ValueError(
            "SKIP_ITEM_INDEX was set, but no ignore file path was set. SKIP_ITEM_INDEX requires IGNORES_FILE"
        )
    if sync_ignore_file and not ign_file_path:
        raise ValueError(
            "SYNC_IGNORE_FILE was set, but no ignore file path was set. SYNC_IGNORE_FILE requires IGNORES_FILE"
        )
    if not dir_path.is_dir():
        raise ValueError(f"Directory does not exist: {dir_path}")

    if temp_dir_env:
        temp_dir = Path(temp_dir_env).resolve()
        if not temp_dir.is_dir():
            raise ValueError(f"Temporary directory does not exist: {temp_dir}")
    else:
        temp_dir = None

    if notify_url_env:
        notify_url = notify_url_env.strip()
        log.info(f"BandcampSync will notify: {notify_url}")
    else:
        notify_url = None

    options = BandcampSyncOptions(
        cookies=cookies,
        dir_path=dir_path,
        media_format=media_format_env,
        temp_dir_root=temp_dir,
        ign_file_path=ign_file_path,
        ign_patterns=ign_patterns,
        notify_url=notify_url,
        until_date=until_date,
        dry_run=dry_run,
        concurrency=concurrency,
        max_retries=max_retries,
        retry_wait=retry_wait,
        skip_item_index=skip_item_index,
        sync_ignore_file=sync_ignore_file,
        skip_hidden=skip_hidden,
    )

    log.info(f"BandcampSync v{version} starting")
    try:
        tz = ZoneInfo(tz_name)
    except Exception as e:
        raise ValueError(f"Not a valid timezone name: {tz_name}") from e
    try:
        run_daily_at = int(run_daily_at_env)
    except TypeError as e:
        raise ValueError(f"Invalid RUN_DAILY_AT, got: {run_daily_at_env}") from e
    if not 0 <= run_daily_at <= 23:
        raise ValueError(
            f"Invalid RUN_DAILY_AT, must be between 0 and 23, got: {run_daily_at}"
        )
    try:
        exit_after_run = int(exit_after_run_env)
    except (ValueError, TypeError):
        exit_after_run = 0
    exit_after_run = True if exit_after_run else False
    time_now = datetime.now(tz).replace(microsecond=0)
    log.info(f"Time now in {tz.key}: {time_now}")
    log.info("Running an initial one-off synchronisation immediately")
    catch_shutdown = CatchShutdownSignal()
    try:
        while not catch_shutdown.shutdown:
            log.info("Starting synchronisation")
            do_sync(options)
            random_delay = randrange(0, 3600)
            time_now = datetime.now(tz).replace(microsecond=0)
            time_tomorrow = time_now + timedelta(days=1)
            time_tomorrow = time_tomorrow.replace(
                hour=run_daily_at, minute=0, second=0, microsecond=0
            )
            next_sleep = int((time_tomorrow - time_now).total_seconds() + random_delay)
            if exit_after_run:
                log.info("Exiting after run")
                break
            log.info(
                f"Scheduling next run for {time_tomorrow} + {random_delay} second random offset"
            )
            log.info(f"Sleeping for {next_sleep} seconds")
            sleep(next_sleep)
    except KeyboardInterrupt:
        log.error("Caught keyboard interrupt, stopping")
    log.info("Done")
