#!/usr/bin/env python3
import argparse
import enum
import getpass
import shutil
import subprocess as sp
from pathlib import Path

import jinja2

PROJECT_ROOT_DIRECTORY = Path(__file__).parent.parent.resolve()

bitwarden_session = None


class TemplateEnvType(enum.StrEnum):
    USERNAME = "username"
    PASSWORD = "password"


class TemplateEnv:
    bw_path: Path
    env_type: TemplateEnvType
    cached_items: dict[str, str]

    def _fetch_secret(self, secret_id: str) -> str:
        global bitwarden_session

        if not bitwarden_session:
            raise NotImplementedError("Failed to get bitwarden session")

        res = sp.run(
            [
                self.bw_path, "get", self.env_type.value,
                secret_id, "--session", bitwarden_session,
            ],
            capture_output=True,
            text=True,
        )
        res.check_returncode()
        return res.stdout

    def __init__(self, _type: TemplateEnvType, bw_path: Path):
        self.env_type = _type
        self.bw_path = bw_path
        self.cached_items = dict()

    def __getitem__(self, item):
        if cached_item := self.cached_items.get(item):
            return cached_item
        self.cached_items[item] = self._fetch_secret(
            item,
        )
        return self.cached_items[item]


def _add_args(parser: argparse.ArgumentParser):
    parser.add_argument(
        "search_paths",
        help="start directory to walk files to find secret references",
        default=[PROJECT_ROOT_DIRECTORY],
        nargs="*",
    )
    parser.add_argument(
        "--bw-path", "-b",
        help="custom path for bitwarden cli executable",
        default=shutil.which("bw"),
    )


def init_bw_session(bw_path: Path):
    global bitwarden_session

    if (pw_file := (PROJECT_ROOT_DIRECTORY / ".bw2secrets")).exists():
        bitwarden_password = pw_file.read_text().strip()
    else:
        print("Please, provide your bitwarden master password")
        bitwarden_password = getpass.getpass("Master password: ")
    res = sp.run(
        [bw_path, "unlock", bitwarden_password, "--raw"], capture_output=True,
        text=True,
    )
    res.check_returncode()
    bitwarden_session = res.stdout


def sync_bw_session(bw_path: Path):
    global bitwarden_session

    if not bitwarden_session:
        raise NotImplementedError("Failed to get bitwarden session")

    res = sp.run(
        [bw_path, "sync", "--session", bitwarden_session], capture_output=True,
        text=True,
    )
    res.check_returncode()


def find_templates(base_dirs: set[Path]) -> set[Path]:
    env_templates: set[Path] = set()
    for path in base_dirs:
        for env_template in path.glob("**/*.template"):
            print(f"INFO: Found template at {env_template}")
            env_templates.add(env_template)
    return env_templates


def compile_file(file_path: Path, bw_path: Path):
    jinja_env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(file_path.parent),
    )
    username = TemplateEnv(TemplateEnvType.USERNAME, bw_path)
    password = TemplateEnv(TemplateEnvType.PASSWORD, bw_path)
    template = jinja_env.get_template(file_path.name)

    rendered_template = template.render(
        dict(
            username=username,
            password=password,
        ),
    )
    file_path.with_name(
        file_path.name.replace(
            ".template", "",
        ),
    ).write_text(rendered_template)


def main() -> int:
    parser = argparse.ArgumentParser("bw2secrets")
    _add_args(parser)
    args = parser.parse_args()

    if not (bw_path := args.bw_path):
        print("Bitwarden CLI `bw` executable not found in PATH")
        return 1

    search_paths: set[Path] = set()
    for path in args.search_paths:
        search_path = Path(path)
        search_paths.add(search_path)
        print(f"INFO: Will be searching {path}")

    print("INFO: Searching templates")
    template_files = find_templates(search_paths)

    init_bw_session(bw_path)
    sync_bw_session(bw_path)

    for file in template_files:
        print(f"INFO: Compiling {file}")
        compile_file(file, bw_path)

    return 0


if __name__ == "__main__":
    raise SystemExit(main())