import json
import os
from inspect import getfile
from pathlib import Path
from tempfile import TemporaryDirectory
from unittest import TestCase, mock

from cally.cdk import CallyStack
from cally.cli.config.service import CallyServiceConfig
from cally.cli.config.terraform_service import CallyStackServiceConfig

from .constants import HOME_ENVS
from .exceptions import CallyTestingTestdataError


class CallyTestHarness(TestCase):
    """Cally provides a Test Harness as good base for building robust unit testing
    for your IDP and IAC. It takes care of ensuring a clean environment, and discrete
    working directory for each unittest, along with providing some useful methods for
    getting testdata or default service objects.
    """

    working: TemporaryDirectory
    _testdata: Path

    def setUp(self):
        self.current_working = Path().cwd()
        self.working = TemporaryDirectory()
        self.env_patcher = mock.patch.dict(
            os.environ,
            {
                HOME_ENVS.get(os.name, 'UNKNOWN'): self.working.name,
                "LC_ALL": os.environ.get('LC_ALL', 'C.UTF-8'),
                "LANG": os.environ.get('LANG', 'C.UTF-8'),
            },
            clear=True,
        )
        self.env_patcher.start()
        os.chdir(self.working.name)

    def tearDown(self):
        self.env_patcher.stop()
        os.chdir(self.current_working)
        self.working.cleanup()

    @property
    def testdata(self) -> Path:
        """Attempts to find a directory called 'testdata' within your tests directory"""
        if getattr(self, '_testdata', None) is None:
            for parent in Path(getfile(self.__class__)).parents:
                testdata = Path(parent, 'testdata')
                if testdata.exists() and testdata.is_dir():
                    self._testdata = testdata
                    return self._testdata
                if parent.is_dir() and parent.name == 'tests':
                    break
            raise CallyTestingTestdataError(
                "No testdata found, expected structure <project_root>/tests/testdata"
            )
        return self._testdata

    @staticmethod
    def get_cally_config(service='test', environment='cally') -> CallyServiceConfig:
        """
        Returns a CallyServiceConfig object, with a service and environment
        pre-configured as 'test' and 'cally'
        """
        config = CallyServiceConfig()
        config.config_file = Path('not-required.yaml')
        config.service = service
        config.environment = environment
        return config

    @staticmethod
    def get_cally_stack_config(
        service='test', environment='cally', stack_type='CallyStack'
    ) -> CallyStackServiceConfig:
        """
        Returns a CallyStackServiceConfig object, with a service and environment
        pre-configured as service='test', environment='cally', stack_type='CallyStack'
        """
        config = CallyStackServiceConfig()
        config.config_file = Path('not-required.yaml')
        config.service = service
        config.environment = environment
        config.settings.stack_type = stack_type
        return config

    def get_test_file(self, filename) -> Path:
        """Loads a the specified file path as a Path object, from the testdata
        directory
        """
        return Path(self.testdata, filename)

    def load_test_file(self, filename) -> str:
        """Loads a testdata file and reads it in as text"""
        return self.get_test_file(filename).read_text()

    def load_json_file(self, filename) -> dict:
        """Loads a json testdata file and returns it as a dictionary"""
        return json.loads(self.load_test_file(filename))


class CallyTfTestHarness(CallyTestHarness):
    """This is an extension of the Cally Test harness, specifically for testing
    Stacks."""

    def synth_stack(self, stack: CallyStack) -> dict:
        """When passed a CallyStack object, synths it and returns the dictionary
        payload generated by the cdktf. Can be used to unit test your stacks.
        """
        stack.synth_stack(self.working.name)
        output_file = Path(self.working.name, 'stacks', stack.name, 'cdk.tf.json')
        return json.loads(output_file.read_text())
