import argparse
import logging
import yaml

from pathlib import Path
from typing import List, Optional

from dataclasses import dataclass


@dataclass
class ServiceSettings:
    nanny_name: str
    service: str
    ctype: str

    perc: Optional[int]
    cnt: Optional[int]
    check_interval: int

    itype: str
    ban_seconds: int
    unban_seconds: int


@dataclass
class FBanConfig:
    services: List[ServiceSettings]
    dry_run: bool

    log_verbosity: int
    log_file: Path
    logrotate_max_bytes: int
    logrotate_backup_count: int

    zk_lock_path: str

    @staticmethod
    def _parse_args() -> argparse.Namespace:
        parser = argparse.ArgumentParser()
        parser.add_argument(
            '-d', '--dry-run', action='store_true', default=False,
            help='Do nothing, just print what will happen'
        )
        parser.add_argument(
            '-s', '--settings', default='settings.yaml',
            help='Settings file path',
        )
        parser.add_argument(
            '-l', '--log-file', default=None,
            help='Log file path'
        )
        parser.add_argument(
            '-lmb', '--logrotate-max-bytes', default=1024*1024*256, type=int,
            help='Logrotate: max log file size in bytes'
        )
        parser.add_argument(
            '-lbc', '--logrotate-backup-count', default=2, type=int,
            help='Logrotate: backup count'
        )
        parser.add_argument(
            '-v', '--verbose', action='count', default=0,
            help='Increasing verbosity level'
        )
        parser.add_argument(
            '--zk-lock-path',
            type=str,
            default='/saas10/locks/fban',
            help='The lock will be taken by the first alive instance (ignored with -d)'
        )

        return parser.parse_args()

    @staticmethod
    def _parse_settings_file(path: str) -> dict:
        data = yaml.safe_load(Path(path).read_text())

        services = []
        for item in data.get('services', []):
            service = ServiceSettings(
                nanny_name=item['nanny_name'],
                service=item['service'],
                ctype=item['ctype'],

                perc=item.get('perc'),
                cnt=item.get('cnt'),
                check_interval=item['check_interval'],

                itype=item.get('itype', 'rtyserver'),  # rtyserver | fusion
                ban_seconds=item.get('ban_minutes', 35) * 60,
                unban_seconds=item.get('unban_minutes', 25) * 60,
            )

            if not bool(service.perc) ^ bool(service.cnt):
                logging.warning(
                    'Either perc or cnt should be set for %s, but not at the same time, ignoring this service...',
                    service.nanny_name
                )
                continue
            elif service.ban_seconds < service.unban_seconds:
                logging.warning(
                    'Ban seconds (%d) overlaps unban seconds (%d) for %s, ignoring this service...',
                    service.ban_seconds, service.unban_seconds, service.nanny_name
                )
                continue

            services.append(service)

        if not services:
            raise ValueError('There are no valid service settings in the settings file')

        logging.info('Discovered %d service(s) in settings', len(services))
        return {'services': services}

    @classmethod
    def _parse(cls) -> 'FBanConfig':
        args = cls._parse_args()
        settings = cls._parse_settings_file(args.settings)

        return FBanConfig(
            dry_run=args.dry_run,

            log_verbosity=args.verbose,
            log_file=Path(args.log_file).resolve() if args.log_file else None,
            logrotate_max_bytes=args.logrotate_max_bytes,
            logrotate_backup_count=args.logrotate_backup_count,

            zk_lock_path=args.zk_lock_path,

            **settings
        )

    @classmethod
    def parse(cls) -> 'FBanConfig':
        try:
            return cls._parse()
        except Exception:
            raise ValueError('Unable to parse given configurations')
