#!/usr/bin/env python3
import sys

if sys.version_info < (3, 8):
    raise RuntimeError("At least Python 3.8 is required")

import logging
from configparser import ConfigParser
from pathlib import Path
from typing import Iterable, Optional


class ClientConfiguration(object):
    @staticmethod
    def ensure_set(s: Optional[str], name: str) -> str:
        if not s:
            raise ValueError(f"Not set: {name}")
        return s

    @property
    def api_base_url(self) -> str:
        raise NotImplementedError

    @property
    def app_name(self) -> str:
        raise NotImplementedError

    @property
    def scopes(self) -> Iterable[str]:
        raise NotImplementedError

    @property
    def client_id(self) -> Optional[str]:
        raise NotImplementedError

    @client_id.setter
    def client_id(self, client_id: str) -> None:
        raise NotImplementedError

    @property
    def client_secret(self) -> Optional[str]:
        raise NotImplementedError

    @client_secret.setter
    def client_secret(self, client_secret: str) -> None:
        raise NotImplementedError

    @property
    def access_token(self) -> Optional[str]:
        raise NotImplementedError

    @access_token.setter
    def access_token(self, access_token: str) -> None:
        raise NotImplementedError


class CPConfiguration(ClientConfiguration):
    BASE_CONFIG = {
        "Mastodon": {
            "api_base_url": "",
            "app_name": "",
            "scopes": "read:statuses read:notifications",
            "client_id": "",
            "client_secret": "",
            "access_token": "",
        }
    }

    def create_parser(self) -> ConfigParser:
        return ConfigParser()

    def read_base_config(self, cparser: ConfigParser) -> None:
        cparser.read_dict(self.BASE_CONFIG)

    def __init__(self, path: Path):
        self.path = Path(path)

        self.cparser = self.create_parser()
        self.read_base_config(self.cparser)
        self.modified = False
        if self.path.is_file():
            self.cparser.read(str(self.path))
        else:
            logging.warning(f"Starting with an empty configuration file: {self.path}")

    def mark_modified(self) -> None:
        self.modified = True

    def save(self) -> None:
        if self.modified:
            with self.path.open("wt") as outf:
                self.cparser.write(outf)
            self.modified = False

    def __getitem__(self, key: str) -> Optional[str]:
        value = self.cparser.get("Mastodon", key)
        if not value:
            return None
        return value

    def __setitem__(self, key: str, value: Optional[str]) -> None:
        if not value:
            value = ""

        if value != self[key]:
            self.modified = True
            self.cparser.set("Mastodon", key, value)

    @property
    def api_base_url(self) -> str:
        return self.ensure_set(self["api_base_url"], "api_base_url")

    @property
    def app_name(self) -> str:
        return self.ensure_set(self["app_name"], "app_name")

    @property
    def scopes(self) -> Iterable[str]:
        value = self.ensure_set(self["scopes"], "scopes")
        generator = (x.strip() for x in value.split())
        values = {x for x in generator if x}
        if not values:
            raise ValueError("Not set: scopes")
        return values

    @property
    def client_id(self) -> Optional[str]:
        return self["client_id"]

    @client_id.setter
    def client_id(self, client_id: str) -> None:
        self["client_id"] = client_id

    @property
    def client_secret(self) -> Optional[str]:
        return self["client_secret"]

    @client_secret.setter
    def client_secret(self, client_secret: str) -> None:
        self["client_secret"] = client_secret

    @property
    def access_token(self) -> Optional[str]:
        return self["access_token"]

    @access_token.setter
    def access_token(self, access_token: str) -> None:
        self["access_token"] = access_token
