import os
import re
from argparse import Namespace
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, List, Optional, Union

import yaml
from webmentions import WebmentionStatus


@dataclass
class Config:
    """
    Configuration for the blog
    """

    title: str = "Blog"
    description: str = ""
    link: str = "/"
    home_link: str = "/"
    rel_me: List[str] = field(default_factory=list)
    external_links: List[Union[str, Dict[str, str]]] = field(default_factory=list)
    nav_links: List[Union[str, Dict[str, Any]]] = field(default_factory=list)
    host: str = "0.0.0.0"
    port: int = 8000
    language: str = "en-US"
    logo: str = "/img/icon.png"
    header: bool = True
    content_dir: str = "."
    categories: List[str] = field(default_factory=list)
    short_feed: bool = False
    max_entries_per_feed: int = 10
    enable_webmentions: bool = True
    webmentions_hard_delete: bool = False
    default_webmention_status: WebmentionStatus = WebmentionStatus.CONFIRMED
    debug: bool = False
    basedir = os.path.abspath(os.path.dirname(__file__))
    author: str | None = None
    author_url: str | None = None
    author_photo: str | None = None
    throttle_seconds_on_update: int = 10
    author_email: str | None = None
    hide_email: bool = False
    smtp_server: str | None = None
    smtp_port: int = 587
    smtp_username: str | None = None
    smtp_password: str | None = None
    smtp_starttls: bool = True
    smtp_enable_starttls_auto: bool = True
    smtp_sender: str | None = None
    view_mode: str = "cards"  # "cards", "list", or "full"
    external_feeds: List[str] = field(default_factory=list)
    external_feeds_as_folders: bool = False
    feeds_cache_expiry_secs: int = 300

    # ActivityPub
    enable_activitypub: bool = False
    activitypub_link: str | None = None
    activitypub_username: str = "blog"
    activitypub_domain: str | None = None
    activitypub_name: str | None = None
    activitypub_summary: str | None = None
    activitypub_icon_url: str | None = None
    activitypub_profile_field_name: str = "Blog"
    activitypub_profile_fields: Dict[str, str] = field(default_factory=dict)
    activitypub_private_key_path: str | None = None
    activitypub_manually_approves_followers: bool = False
    activitypub_description_only: bool = False
    activitypub_object_type: str = "Note"
    activitypub_posts_content_wrapped: bool = False
    activitypub_email_notifications: bool = True
    activitypub_quote_control: str = "public"
    activitypub_auto_approve_quotes: bool = True

    # Guestbook
    enable_guestbook: bool = True

    # Rendering features
    enable_latex: bool = True
    enable_mermaid: bool = True

    # Moderation
    blocked_actors: List[str] = field(default_factory=list)
    allowed_actors: List[str] = field(default_factory=list)

    # State directory
    state_dir: str | None = None  # None means use default: <content_dir>/.madblog

    # Visibility
    default_visibility: str = "public"

    # Default index page: "blog", "about", "tags", "unlisted" or "guestbook"
    default_index: str = "blog"

    @property
    def resolved_state_dir(self) -> Path:
        """Return the resolved state directory path."""
        if self.state_dir:
            return Path(self.state_dir).expanduser().resolve()
        return Path(self.content_dir).expanduser().resolve() / ".madblog"

    @property
    def templates_dir(self) -> str:
        return os.path.join(self.basedir, "templates")

    @property
    def static_dir(self) -> str:
        return os.path.join(self.basedir, "static")

    @property
    def default_css_dir(self) -> str:
        return os.path.join(self.static_dir, "css")

    @property
    def default_js_dir(self) -> str:
        return os.path.join(self.static_dir, "js")

    @property
    def default_fonts_dir(self) -> str:
        return os.path.join(self.static_dir, "fonts")

    @property
    def default_img_dir(self) -> str:
        return os.path.join(self.static_dir, "img")

    @property
    def activitypub_profile_url(self) -> Optional[str]:
        if not self.enable_activitypub:
            return None

        base = self.link.rstrip("/")
        return f"{base}/@{self.activitypub_username}"

    @property
    def activitypub_actor_url(self) -> Optional[str]:
        if not self.enable_activitypub:
            return None

        base = (self.activitypub_link or self.link).rstrip("/")
        return f"{base}/ap/actor"

    @property
    def webmention_url(self) -> Optional[str]:
        from flask import request

        if not self.enable_webmentions:
            return None

        return (
            f'{self.link.rstrip("/")}/webmentions'
            if re.match(r"^https?://", self.link)
            else f'{request.host_url.rstrip("/")} /webmentions'
        )


config = Config()


def _init_config_from_file(  # pylint: disable=too-many-branches,too-many-statements
    config_file: str,
):
    cfg = {}

    if os.path.isfile(config_file):
        with open(config_file, "r") as f:
            cfg = yaml.safe_load(f) or {}

    if cfg.get("content_dir"):
        config.content_dir = cfg["content_dir"]
    if cfg.get("title"):
        config.title = cfg["title"]
    if cfg.get("description"):
        config.description = cfg["description"]
    if cfg.get("link"):
        config.link = cfg["link"]
    if cfg.get("home_link"):
        config.home_link = cfg["home_link"]
    if cfg.get("rel_me"):
        config.rel_me = cfg["rel_me"]
    if cfg.get("external_links"):
        config.external_links = cfg["external_links"]
    if cfg.get("nav_links"):
        config.nav_links = cfg["nav_links"]
    if cfg.get("host"):
        config.host = cfg["host"]
    if cfg.get("port"):
        config.port = int(cfg["port"])
    if cfg.get("logo") is not None:
        config.logo = cfg["logo"]
    if cfg.get("language"):
        config.language = cfg["language"]
    if cfg.get("header"):
        config.header = bool(cfg["header"])
    if cfg.get("short_feed"):
        config.short_feed = bool(cfg["short_feed"])
    if cfg.get("max_entries_per_feed") is not None:
        config.max_entries_per_feed = int(cfg["max_entries_per_feed"])
    if cfg.get("enable_webmentions") is not None:
        config.enable_webmentions = bool(cfg["enable_webmentions"])
    if cfg.get("webmentions_hard_delete") is not None:
        config.webmentions_hard_delete = bool(cfg["webmentions_hard_delete"])
    if cfg.get("default_webmention_status"):
        config.default_webmention_status = WebmentionStatus(
            cfg["default_webmention_status"]
        )
    if cfg.get("debug") is not None:
        config.debug = bool(cfg["debug"])
    if cfg.get("author"):
        config.author = cfg["author"]
    if cfg.get("author_url"):
        config.author_url = cfg["author_url"]
    if cfg.get("author_photo"):
        config.author_photo = cfg["author_photo"]
    if cfg.get("throttle_seconds_on_update"):
        config.throttle_seconds_on_update = int(cfg["throttle_seconds_on_update"])
    if cfg.get("author_email"):
        config.author_email = cfg["author_email"]
    if cfg.get("hide_email") is not None:
        config.hide_email = bool(cfg["hide_email"])
    if cfg.get("smtp_server"):
        config.smtp_server = cfg["smtp_server"]
    if cfg.get("smtp_port"):
        config.smtp_port = int(cfg["smtp_port"])
    if cfg.get("smtp_username"):
        config.smtp_username = cfg["smtp_username"]
    if cfg.get("smtp_password"):
        config.smtp_password = cfg["smtp_password"]
    if cfg.get("smtp_starttls") is not None:
        config.smtp_starttls = bool(cfg["smtp_starttls"])
    if cfg.get("smtp_enable_starttls_auto") is not None:
        config.smtp_enable_starttls_auto = bool(cfg["smtp_enable_starttls_auto"])
    if cfg.get("smtp_sender"):
        config.smtp_sender = cfg["smtp_sender"]
    if cfg.get("view_mode"):
        config.view_mode = cfg["view_mode"]
    if cfg.get("external_feeds"):
        config.external_feeds = cfg["external_feeds"]
    if cfg.get("external_feeds_as_folders") is not None:
        config.external_feeds_as_folders = bool(cfg["external_feeds_as_folders"])
    if cfg.get("feeds_cache_expiry_secs"):
        config.feeds_cache_expiry_secs = int(cfg["feeds_cache_expiry_secs"])
    config.categories = cfg.get("categories", [])

    # ActivityPub
    if cfg.get("enable_activitypub") is not None:
        config.enable_activitypub = bool(cfg["enable_activitypub"])
    if cfg.get("activitypub_link"):
        config.activitypub_link = cfg["activitypub_link"]
    if cfg.get("activitypub_username"):
        config.activitypub_username = cfg["activitypub_username"]
    if cfg.get("activitypub_domain"):
        config.activitypub_domain = cfg["activitypub_domain"]
    if cfg.get("activitypub_name"):
        config.activitypub_name = cfg["activitypub_name"]
    if cfg.get("activitypub_summary"):
        config.activitypub_summary = cfg["activitypub_summary"]
    if cfg.get("activitypub_icon_url"):
        config.activitypub_icon_url = cfg["activitypub_icon_url"]
    if cfg.get("activitypub_profile_field_name"):
        config.activitypub_profile_field_name = str(
            cfg["activitypub_profile_field_name"]
        )

    if cfg.get("activitypub_profile_fields") is not None:
        fields = cfg["activitypub_profile_fields"]
        if isinstance(fields, dict):
            config.activitypub_profile_fields = {
                str(k): str(v)
                for k, v in fields.items()
                if k is not None and v is not None
            }
        else:
            raise ValueError(
                "activitypub_profile_fields must be a mapping of name->value"
            )
    if cfg.get("activitypub_private_key_path"):
        config.activitypub_private_key_path = cfg["activitypub_private_key_path"]
    if cfg.get("activitypub_manually_approves_followers") is not None:
        config.activitypub_manually_approves_followers = bool(
            cfg["activitypub_manually_approves_followers"]
        )
    if cfg.get("activitypub_description_only") is not None:
        config.activitypub_description_only = bool(cfg["activitypub_description_only"])
    if cfg.get("activitypub_object_type"):
        config.activitypub_object_type = cfg["activitypub_object_type"]
    if cfg.get("activitypub_posts_content_wrapped") is not None:
        config.activitypub_posts_content_wrapped = bool(
            cfg["activitypub_posts_content_wrapped"]
        )
    if cfg.get("activitypub_email_notifications") is not None:
        config.activitypub_email_notifications = bool(
            cfg["activitypub_email_notifications"]
        )
    if cfg.get("activitypub_quote_control"):
        config.activitypub_quote_control = cfg["activitypub_quote_control"]
    if cfg.get("activitypub_auto_approve_quotes") is not None:
        config.activitypub_auto_approve_quotes = bool(
            cfg["activitypub_auto_approve_quotes"]
        )
    config.categories = cfg.get("categories", [])

    # Guestbook
    if cfg.get("enable_guestbook") is not None:
        config.enable_guestbook = bool(cfg["enable_guestbook"])

    # Rendering features
    if cfg.get("enable_latex") is not None:
        config.enable_latex = bool(cfg["enable_latex"])
    if cfg.get("enable_mermaid") is not None:
        config.enable_mermaid = bool(cfg["enable_mermaid"])

    # Moderation
    if cfg.get("blocked_actors"):
        config.blocked_actors = list(cfg["blocked_actors"])
    if cfg.get("allowed_actors"):
        config.allowed_actors = list(cfg["allowed_actors"])

    # State directory
    if cfg.get("state_dir"):
        config.state_dir = cfg["state_dir"]

    # Visibility
    if cfg.get("default_visibility"):
        config.default_visibility = cfg["default_visibility"]

    # Default index page
    if cfg.get("default_index"):
        config.default_index = cfg["default_index"]


def _init_config_from_env():  # pylint: disable=too-many-branches,too-many-statements
    if os.getenv("MADBLOG_TITLE"):
        config.title = os.environ["MADBLOG_TITLE"]
    if os.getenv("MADBLOG_DESCRIPTION"):
        config.description = os.environ["MADBLOG_DESCRIPTION"]
    if os.getenv("MADBLOG_LINK"):
        config.link = os.environ["MADBLOG_LINK"]
    if os.getenv("MADBLOG_HOME_LINK"):
        config.home_link = os.environ["MADBLOG_HOME_LINK"]
    if os.getenv("MADBLOG_REL_ME"):
        config.rel_me = re.split(r"[,\s]+", os.environ["MADBLOG_REL_ME"].strip())
    if os.getenv("MADBLOG_EXTERNAL_LINKS"):
        config.external_links = re.split(
            r"[,\s]+", os.environ["MADBLOG_EXTERNAL_LINKS"].strip()
        )
    if os.getenv("MADBLOG_NAV_LINKS"):
        config.nav_links = re.split(r"[,\s]+", os.environ["MADBLOG_NAV_LINKS"].strip())
    if os.getenv("MADBLOG_HOST"):
        config.host = os.environ["MADBLOG_HOST"]
    if os.getenv("MADBLOG_PORT"):
        config.port = int(os.environ["MADBLOG_PORT"])
    if os.getenv("MADBLOG_CONTENT_DIR"):
        config.content_dir = os.environ["MADBLOG_CONTENT_DIR"]
    if os.getenv("MADBLOG_LOGO"):
        config.logo = os.environ["MADBLOG_LOGO"]
    if os.getenv("MADBLOG_LANGUAGE"):
        config.language = os.environ["MADBLOG_LANGUAGE"]
    if os.getenv("MADBLOG_HEADER"):
        config.header = os.environ["MADBLOG_HEADER"] == "1"
    if os.getenv("MADBLOG_SHORT_FEED"):
        config.short_feed = os.environ["MADBLOG_SHORT_FEED"] == "1"
    if os.getenv("MADBLOG_MAX_ENTRIES_PER_FEED"):
        config.max_entries_per_feed = int(os.environ["MADBLOG_MAX_ENTRIES_PER_FEED"])
    if os.getenv("MADBLOG_ENABLE_WEBMENTIONS"):
        config.enable_webmentions = os.environ["MADBLOG_ENABLE_WEBMENTIONS"] == "1"
    if os.getenv("MADBLOG_WEBMENTIONS_HARD_DELETE"):
        config.webmentions_hard_delete = (
            os.environ["MADBLOG_WEBMENTIONS_HARD_DELETE"] == "1"
        )
    if os.getenv("MADBLOG_DEFAULT_WEBMENTION_STATUS"):
        config.default_webmention_status = WebmentionStatus(
            os.environ["MADBLOG_DEFAULT_WEBMENTION_STATUS"]
        )
    if os.getenv("MADBLOG_DEBUG"):
        config.debug = os.environ["MADBLOG_DEBUG"] == "1"
    if os.getenv("MADBLOG_VIEW_MODE"):
        config.view_mode = os.environ["MADBLOG_VIEW_MODE"]
    if os.getenv("MADBLOG_CATEGORIES"):
        config.categories = re.split(r"[,\s]+", os.environ["MADBLOG_CATEGORIES"])
    if os.getenv("MADBLOG_AUTHOR"):
        config.author = os.environ["MADBLOG_AUTHOR"]
    if os.getenv("MADBLOG_AUTHOR_URL"):
        config.author_url = os.environ["MADBLOG_AUTHOR_URL"]
    if os.getenv("MADBLOG_AUTHOR_PHOTO"):
        config.author_photo = os.environ["MADBLOG_AUTHOR_PHOTO"]
    if os.getenv("MADBLOG_THROTTLE_SECONDS_ON_UPDATE"):
        config.throttle_seconds_on_update = int(
            os.environ["MADBLOG_THROTTLE_SECONDS_ON_UPDATE"]
        )
    if os.getenv("MADBLOG_AUTHOR_EMAIL"):
        config.author_email = os.environ["MADBLOG_AUTHOR_EMAIL"]
    if os.getenv("MADBLOG_HIDE_EMAIL"):
        config.hide_email = os.environ["MADBLOG_HIDE_EMAIL"] == "1"
    if os.getenv("MADBLOG_SMTP_SERVER"):
        config.smtp_server = os.environ["MADBLOG_SMTP_SERVER"]
    if os.getenv("MADBLOG_SMTP_PORT"):
        config.smtp_port = int(os.environ["MADBLOG_SMTP_PORT"])
    if os.getenv("MADBLOG_SMTP_USERNAME"):
        config.smtp_username = os.environ["MADBLOG_SMTP_USERNAME"]
    if os.getenv("MADBLOG_SMTP_PASSWORD"):
        config.smtp_password = os.environ["MADBLOG_SMTP_PASSWORD"]
    if os.getenv("MADBLOG_SMTP_STARTTLS"):
        config.smtp_starttls = os.environ["MADBLOG_SMTP_STARTTLS"] == "1"
    if os.getenv("MADBLOG_SMTP_ENABLE_STARTTLS_AUTO"):
        config.smtp_enable_starttls_auto = (
            os.environ["MADBLOG_SMTP_ENABLE_STARTTLS_AUTO"] == "1"
        )
    if os.getenv("MADBLOG_SMTP_SENDER"):
        config.smtp_sender = os.environ["MADBLOG_SMTP_SENDER"]
    if os.getenv("MADBLOG_EXTERNAL_FEEDS"):
        config.external_feeds = re.split(
            r"[,\s]+", os.environ["MADBLOG_EXTERNAL_FEEDS"].strip()
        )
    if os.getenv("MADBLOG_EXTERNAL_FEEDS_AS_FOLDERS"):
        config.external_feeds_as_folders = os.environ[
            "MADBLOG_EXTERNAL_FEEDS_AS_FOLDERS"
        ].lower() in ("1", "true", "yes")
    if os.getenv("MADBLOG_FEEDS_CACHE_EXPIRY_SECS"):
        config.feeds_cache_expiry_secs = int(
            os.environ["MADBLOG_FEEDS_CACHE_EXPIRY_SECS"]
        )

    # ActivityPub
    if os.getenv("MADBLOG_ENABLE_ACTIVITYPUB"):
        config.enable_activitypub = os.environ["MADBLOG_ENABLE_ACTIVITYPUB"] == "1"
    if os.getenv("MADBLOG_ACTIVITYPUB_LINK"):
        config.activitypub_link = os.environ["MADBLOG_ACTIVITYPUB_LINK"]
    if os.getenv("MADBLOG_ACTIVITYPUB_USERNAME"):
        config.activitypub_username = os.environ["MADBLOG_ACTIVITYPUB_USERNAME"]
    if os.getenv("MADBLOG_ACTIVITYPUB_DOMAIN"):
        config.activitypub_domain = os.environ["MADBLOG_ACTIVITYPUB_DOMAIN"]
    if os.getenv("MADBLOG_ACTIVITYPUB_NAME"):
        config.activitypub_name = os.environ["MADBLOG_ACTIVITYPUB_NAME"]
    if os.getenv("MADBLOG_ACTIVITYPUB_SUMMARY"):
        config.activitypub_summary = os.environ["MADBLOG_ACTIVITYPUB_SUMMARY"]
    if os.getenv("MADBLOG_ACTIVITYPUB_ICON_URL"):
        config.activitypub_icon_url = os.environ["MADBLOG_ACTIVITYPUB_ICON_URL"]
    if os.getenv("MADBLOG_ACTIVITYPUB_PRIVATE_KEY_PATH"):
        config.activitypub_private_key_path = os.environ[
            "MADBLOG_ACTIVITYPUB_PRIVATE_KEY_PATH"
        ]
    if os.getenv("MADBLOG_ACTIVITYPUB_MANUALLY_APPROVES_FOLLOWERS"):
        config.activitypub_manually_approves_followers = (
            os.environ["MADBLOG_ACTIVITYPUB_MANUALLY_APPROVES_FOLLOWERS"] == "1"
        )
    if os.getenv("MADBLOG_ACTIVITYPUB_DESCRIPTION_ONLY"):
        config.activitypub_description_only = (
            os.environ["MADBLOG_ACTIVITYPUB_DESCRIPTION_ONLY"] == "1"
        )
    if os.getenv("MADBLOG_ACTIVITYPUB_OBJECT_TYPE"):
        config.activitypub_object_type = os.environ["MADBLOG_ACTIVITYPUB_OBJECT_TYPE"]
    if os.getenv("MADBLOG_ACTIVITYPUB_POSTS_CONTENT_WRAPPED"):
        config.activitypub_posts_content_wrapped = (
            os.environ["MADBLOG_ACTIVITYPUB_POSTS_CONTENT_WRAPPED"] == "1"
        )
    if os.getenv("MADBLOG_ACTIVITYPUB_EMAIL_NOTIFICATIONS"):
        config.activitypub_email_notifications = (
            os.environ["MADBLOG_ACTIVITYPUB_EMAIL_NOTIFICATIONS"] == "1"
        )
    if os.getenv("MADBLOG_ACTIVITYPUB_QUOTE_CONTROL"):
        config.activitypub_quote_control = os.environ[
            "MADBLOG_ACTIVITYPUB_QUOTE_CONTROL"
        ]
    if os.getenv("MADBLOG_ACTIVITYPUB_AUTO_APPROVE_QUOTES"):
        config.activitypub_auto_approve_quotes = (
            os.environ["MADBLOG_ACTIVITYPUB_AUTO_APPROVE_QUOTES"] == "1"
        )
    # Guestbook
    if os.getenv("MADBLOG_ENABLE_GUESTBOOK"):
        config.enable_guestbook = os.environ["MADBLOG_ENABLE_GUESTBOOK"] == "1"

    # Rendering features
    if os.getenv("MADBLOG_ENABLE_LATEX"):
        config.enable_latex = os.environ["MADBLOG_ENABLE_LATEX"] == "1"
    if os.getenv("MADBLOG_ENABLE_MERMAID"):
        config.enable_mermaid = os.environ["MADBLOG_ENABLE_MERMAID"] == "1"

    # Moderation
    if os.getenv("MADBLOG_BLOCKED_ACTORS"):
        config.blocked_actors = re.split(
            r"[,\s]+", os.environ["MADBLOG_BLOCKED_ACTORS"].strip()
        )
    if os.getenv("MADBLOG_ALLOWED_ACTORS"):
        config.allowed_actors = re.split(
            r"[,\s]+", os.environ["MADBLOG_ALLOWED_ACTORS"].strip()
        )

    if os.getenv("MADBLOG_ACTIVITYPUB_PROFILE_FIELD_NAME"):
        config.activitypub_profile_field_name = os.environ[
            "MADBLOG_ACTIVITYPUB_PROFILE_FIELD_NAME"
        ]

    # State directory
    if os.getenv("MADBLOG_STATE_DIR"):
        config.state_dir = os.environ["MADBLOG_STATE_DIR"]

    # Visibility
    if os.getenv("MADBLOG_DEFAULT_VISIBILITY"):
        config.default_visibility = os.environ["MADBLOG_DEFAULT_VISIBILITY"]

    # Default index page
    if os.getenv("MADBLOG_DEFAULT_INDEX"):
        config.default_index = os.environ["MADBLOG_DEFAULT_INDEX"]


def _init_config_from_cli(args: Optional[Namespace]):
    if not args:
        return

    if args.dir:
        config.content_dir = args.dir
    if args.host:
        config.host = args.host
    if args.port:
        config.port = args.port
    if args.debug is not None:
        config.debug = args.debug


def init_config(
    config_file: str = "config.yaml", args: Optional[Namespace] = None
) -> Config:
    """
    Initialize the configuration, reading from a file and environment variables.
    """

    config_file = os.path.abspath(os.path.expanduser(config_file))
    _init_config_from_file(config_file)
    _init_config_from_env()
    _init_config_from_cli(args)

    # Normalize/expand paths
    config.content_dir = os.path.abspath(os.path.expanduser(config.content_dir))

    return config


# vim:sw=4:ts=4:et:
