from rich.markdown import Markdown
from typer import Argument, Option, Typer

from kolombo import conf
from kolombo.bin.util import (
    CliLog,
    async_command,
    build_kolombo_image,
    create_network,
    kolombo_image_exists,
    run_container,
)
from kolombo.models import Domain
from kolombo.resources import init_database

cli = Typer()


@cli.command("list")
@async_command
async def list_domains(
    conf: str = Option(None, help="Path to .env file with configuration")  # noqa: B008
) -> None:
    log = CliLog()
    await init_database()

    all_domains = await Domain.all_active()
    active_pairs = [f"{domain.mx} -> {domain.actual}" for domain in all_domains]
    log.step(f"Active domains: {len(active_pairs)}")
    if len(active_pairs) > 0:
        log.info(Markdown(f"*  {'* '.join(active_pairs)}"))


async def _update_virtual_domains() -> None:
    domains_list = "\n".join(domain.actual for domain in await Domain.all_active())
    with open("/etc/kolombo/virtual/domains", mode="w") as domains_file:
        domains_file.write(f"# File is auto-generated\n{domains_list}\n")


async def _update_virtual_ssl_map() -> None:
    domains_mx = [domain.mx for domain in await Domain.all_active()]
    ssl_map = "\n".join(
        f"{mx} /etc/letsencrypt/live/{mx}/privkey.pem "
        f"/etc/letsencrypt/live/{mx}/fullchain.pem"
        for mx in domains_mx
    )
    with open("/etc/kolombo/virtual/ssl_map", mode="w") as ssl_map_file:
        ssl_map_file.write(f"{ssl_map}\n")


_nginx_config = """# File was auto-generated by kolombo
# Receive-only SMTP & IMAP mail reading
server {
    server_name {{MX_DOMAIN}};
    ssl_certificate /etc/letsencrypt/live/{{MX_DOMAIN}}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{MX_DOMAIN}}/privkey.pem;
    auth_http_header X-Secret-Key {{SECRET_KEY}};
    auth_http_header X-Domain {{ACTUAL_DOMAIN}};
    auth_http kolombo-auth:7089/auth;

    listen 993 ssl;
    protocol imap;
}

# Send-only SMTP
server {
    server_name {{MX_DOMAIN}};
    ssl_certificate /etc/letsencrypt/live/{{MX_DOMAIN}}/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/{{MX_DOMAIN}}/privkey.pem;
    auth_http_header X-Secret-Key {{SECRET_KEY}};
    auth_http_header X-Domain {{ACTUAL_DOMAIN}};
    auth_http kolombo-auth:7089/auth;

    listen 587 ssl;
    protocol smtp;
}
"""


def _add_mail_enabled_config(domain: str, mx: str) -> None:
    config = _nginx_config.replace("{{ACTUAL_DOMAIN}}", domain)
    config = config.replace("{{MX_DOMAIN}}", mx)
    config = config.replace("{{SECRET_KEY}}", conf.NGINX_SECRET_KEY)
    with open(f"/etc/kolombo/mail-enabled/{mx}.conf", mode="w") as nginx_file:
        nginx_file.write(config)


def _generate_dkim_key(domain: str) -> str:
    create_network("kolombo", subnet="192.168.79.0/24")
    return run_container(
        "kolombo-sender",
        args=f"gen_key {domain}",
        name=f"kolombo-{domain}-sender",
        net="kolombo",
        volumes=["/etc/kolombo/dkim_keys:/etc/opendkim/keys"],
        # Remove container after stop, attach interactive TTY
        other_flags="--rm -it",
    )


def _parse_txt_record(output: str) -> str:
    txt_record = output[output.find("(") + 1 : output.find(")")]  # noqa: E203
    return txt_record.replace('"\n\t  "', "")


@cli.command("add")
@async_command
async def add_domain(
    conf: str = Option(None, help="Path to .env file with configuration"),  # noqa: B008
    domain: str = Argument(..., help="Domain that come after @ in email"),  # noqa: B008
    mx: str = Argument(None, help="Domain from DNS MX record if exists"),  # noqa: B008
) -> None:
    log = CliLog()
    await init_database()

    if mx is None:
        mx = domain

    if not domain or not mx:
        log.error("Arguments MUST NOT be empty string")
        exit(1)
    elif await Domain.objects.filter(actual=domain, mx=mx).exists():
        log.error(f"Domain pair '{mx} -> {domain}' exists")
        exit(2)

    log.step("- Adding config to mail-enabled")
    _add_mail_enabled_config(domain, mx)
    # TODO: Do postfix reload in kolombo-receiver
    log.step("- Building kolombo-sender for DKIM key generation (if does not exist)")
    if not kolombo_image_exists(component="sender"):
        build_kolombo_image(component="sender")

    log.step("- Generating DKIM key ([u]save TXT DNS record from output[/u])")
    output = _generate_dkim_key(domain)
    log.info(f"mail._domainkey.{domain} TXT: {_parse_txt_record(output)}")
    log.step(f"- Adding pair '{mx} -> {domain}' to database")
    await Domain.objects.create(actual=domain, mx=mx)
    log.step("- Updating virtual domains")
    await _update_virtual_domains()
    await _update_virtual_ssl_map()
    log.step(f"Domain pair '{mx} -> {domain}' added!")
