"""Generate JSON which Terraform can use from Python."""

from collections.abc import Iterable
from importlib import import_module
from json import dump
from pathlib import Path
from subprocess import check_output
from typing import Any, TypeVar

from cdktf import App, TerraformElement, TerraformStack
from constructs import Construct, Node
from tap import Tap


class HeliStack(TerraformStack):
    def __init__(self, cona: str) -> None:
        # Something is automatically creating outdir, which is cdktf.out by default
        super().__init__(App(outdir='.'), cona)

        self.cona = cona
        self.imports: dict[str, str] = {}  # {to_0: id_0, to_1, id_1, ...}
        self._scopes: dict[str, Construct] = {}

    def _allocate_logical_id(self, element: Node | TerraformElement) -> str:
        if isinstance(element, Node):
            # Mostly for mypy. Patches to support AWS CDK welcome.
            raise TypeError('AWS CDK unsupported; please use CDKTF')
        return element.node.id

    def provide(self, name: str) -> type[TerraformElement]:
        """
        Return a Provider class instance given its short name.

        Example usage:
        stack.provide('cloudflare')
        """
        module = import_module(f'cdktf_cdktf_provider_{name}.provider')
        return getattr(module, f'{name.title()}Provider')(self, 'this')

    E = TypeVar('E', bound=TerraformElement)

    def push(
        self,
        Element: type[E],  # noqa: N803
        id_: str,
        *args: Any,  # noqa: ANN401
        import_id: str = '',
        **kwargs: Any,  # noqa: ANN401
    ) -> E:
        """
        Add Data or a Resource to the stack and return it.

        Example usage:
        from cdktf_cdktf_provider_cloudflare.access_application import AccessApplication
        stack.push(AccessApplication, 'mydomain-wildcard', domain='*.mydomain.com')
        """
        if Element.__module__ == 'cdktf':
            scope_name = Element.__name__.lower().replace('terraform', '')
        else:
            scope_name = Element.__module__.replace('cdktf_cdktf_provider_', '').replace('.', '_')
        if scope_name not in self._scopes:
            self._scopes[scope_name] = Construct(self, scope_name)

        print(f'Pushing {scope_name}.{id_}')
        element = Element(self._scopes[scope_name], id_, *args, **kwargs)
        if import_id:
            self.imports[
                f"{Element.__module__.replace('cdktf_cdktf_provider_', '').replace('.', '_')}.{id_}"
            ] = import_id
        return element


# ruff: noqa: T201
def multisynth(
    all_or_conas_or_paths: Iterable[str], *, hashicorp_configuration_language: bool = False
) -> None:
    if not all_or_conas_or_paths:
        print('No codenames specified. Doing nothing.')
        return

    top_directory = Path(__file__).parent

    if 'all' in all_or_conas_or_paths or 'helicopyter.py' in all_or_conas_or_paths:
        conas_or_paths = {
            file.parent.parent.name
            for file in (top_directory / 'deploys').glob('**/terraform/main.py')
        }
    else:
        conas_or_paths = set(all_or_conas_or_paths)

    for cona_or_path in conas_or_paths:
        path_to_check = Path(cona_or_path)
        if (
            path_to_check.exists()
            and path_to_check.name == 'main.py'
            and path_to_check.parent.name == 'terraform'
        ):
            cona = path_to_check.parent.parent.name
        else:
            cona = cona_or_path
        relative_path = (
            f'deploys/{cona}/terraform/main.tf{"" if hashicorp_configuration_language else ".json"}'
        )
        print(f'Generating {relative_path}')
        module = import_module(f'deploys.{cona}.terraform.main')
        stack = HeliStack(cona)
        module.synth(stack)
        if hashicorp_configuration_language:
            unformatted = stack.to_hcl_terraform()['hcl'].encode()
            formatted = check_output(['terraform', 'fmt', '-'], input=unformatted).decode()  # noqa: S603
            (top_directory / relative_path).write_text(
                '# AUTOGENERATED by helicopyter\n\n' + formatted
            )
        else:
            dictionary = stack.to_terraform()
            dictionary['//']['AUTOGENERATED'] = 'by helicopyter'
            if stack.imports:
                dictionary['import'] = [
                    {'to': resource, 'id': import_id}
                    for resource, import_id in stack.imports.items()
                ]
            with (top_directory / relative_path).open('w') as output:
                dump(dictionary, output, indent=4, sort_keys=True)
                output.write('\n')


class Parameters(Tap):
    conas: list[str]  # space-separated COdeNAmes
    hashicorp_configuration_language: bool = False

    def configure(self) -> None:  # noqa: D102
        self.add_argument('conas')  # Make this a positional argument


if __name__ == '__main__':
    args = Parameters().parse_args()
    multisynth(args.conas, hashicorp_configuration_language=args.hashicorp_configuration_language)
