import argparse
import os
import re
from pathlib import Path


class DynamicCompose:
    CWD = None
    SAFELY_COVER_MARK = ('# This file was automatically generated by DynamicCompose, and can be safely deleted, '
                         'but do not edit it or remove this comment.\n')

    def __init__(self, show_command: bool = True, force_cover: bool = False):
        self.show_command = show_command
        self.force_cover = force_cover
        self.cwd = self.CWD or Path().cwd()
        self.env_file = self.cwd / '.env'
        self.env_file.touch(exist_ok=True)
        self.compose_dir = self.cwd / '.compose'
        self.compose_dir.mkdir(exist_ok=True)
        self.compose_file = self.cwd / 'compose.yml'
        self.dockerfile_file = self.cwd / 'Dockerfile'
        self.compose_content = ''
        self.dockerfile_content = ''

        self.env = self._get_env()
        args, self.extra_args = self._get_args()
        self.env.update(dict(args.e or ()))
        self.arg = {}  # Args used in building

    @staticmethod
    def _parse_key_value(arg_string: str):
        key, value = arg_string.strip().split('=', maxsplit=1)
        return key, value

    def _get_env(self):
        env_dict = {}
        if self.env_file.exists():
            with open(self.env_file) as f:
                for line in f.readlines():
                    if line.strip():
                        key, value = self._parse_key_value(line)
                        env_dict[key] = value
        return env_dict

    def _get_args(self) -> tuple[argparse.Namespace, list[str]]:
        parser = argparse.ArgumentParser(description='Dynamic generating the Dockerfile and the compose.yml')
        parser.add_argument('-e', action='append', type=self._parse_key_value,
                            help='The environment variables are used in generating.')
        parsed_args, parsed_extra_args = parser.parse_known_args()
        return parsed_args, parsed_extra_args

    @staticmethod
    def _convert_env_var_ref(template: str) -> str:
        return re.sub(r'\$\{(.+?)}', r'${{\1}}', template)

    def format_compose(self, template: str = None, template_file: str | Path = None) -> str:
        if not template:
            template = Path(template_file or self.compose_dir / 'compose.yml').read_text(encoding='utf-8')
        self.compose_content = self._convert_env_var_ref(template).format(**self.env, **self.arg)
        return self.compose_content

    def format_dockerfile(self, template: str = None, template_file: str | Path = None) -> str:
        if not template:
            template = Path(template_file or self.compose_dir / 'Dockerfile').read_text(encoding='utf-8')
        self.dockerfile_content = self._convert_env_var_ref(template).format(**self.env, **self.arg)
        return self.dockerfile_content

    def dump_compose(self, target_file: str | Path = None) -> None:
        Path(target_file or self.compose_file).write_text(self.SAFELY_COVER_MARK + self.compose_content,
                                                          encoding='utf-8')

    def dump_dockerfile(self, target_file: str | Path = None) -> None:
        Path(target_file or self.dockerfile_file).write_text(self.SAFELY_COVER_MARK + self.dockerfile_content,
                                                             encoding='utf-8')

    def fd_compose(self, template: str = None, template_file: str | Path = None,
                   target_file: str | Path = None) -> None:
        self.format_compose(template, template_file)
        self.dump_compose(target_file)

    def fd_dockerfile(self, template: str = None, template_file: str | Path = None,
                      target_file: str | Path = None) -> None:
        self.format_dockerfile(template, template_file)
        self.dump_dockerfile(target_file)

    def dump(self) -> None:
        self.dump_compose()
        self.dump_dockerfile()

    def up(self, with_extra_args: bool = True) -> int:
        command = f'docker compose up'
        if with_extra_args:
            command = f'{command} {" ".join(self.extra_args)}'
        if self.show_command:
            print(command)
        return os.system(command)
