import os
from inspect import getsourcefile
from shutil import copy, copyfile, rmtree
from typing import Dict

import toml
from fastapi.routing import APIRoute
from pydash import group_by
from ssort import ssort
from starlette.routing import BaseRoute

from .base_microservice_client import BaseMicroserviceClient
from .models.method_detail import MethodDetail
from .sdk_template import SdkTemplate


class SdkBuilder:
    def generate(self, routes: list[BaseRoute]) -> None:
        root_path = os.path.abspath('.')
        sdk_path = os.path.join(root_path, 'sdk')
        self.create_sdk_dir(sdk_path)
        self.clone_static_files(root_path, sdk_path)
        sdk_detail = self.get_sdk_detail(root_path)
        project_name = sdk_detail['project']['name']
        self.create_source_dir(sdk_path, project_name)
        source_path = os.path.join(sdk_path, 'src', project_name)
        self.generate_init_file(source_path)
        self.generate_code(project_name, source_path, routes)

    def get_sdk_detail(self, root_path: str):
        with open(os.path.join(root_path, 'sdk.toml'), 'r') as file:
            return toml.load(file)

    def create_sdk_dir(self, sdk_path: str):
        if os.path.exists(sdk_path):
            rmtree(sdk_path)
        os.mkdir(sdk_path)

    def create_source_dir(self, sdk_path: str, project_name: str):
        src_path = os.path.join(sdk_path, 'src')
        os.mkdir(src_path)
        os.mkdir(os.path.join(src_path, project_name))

    def generate_init_file(self, source_path: str):
        content = 'from .sdk import *'
        with open(os.path.join(source_path, '__init__.py'), "w+") as file:
            file.write(content)

    def clone_static_files(self, root_path: str, sdk_path: str):
        files = ['.gitignore']
        for file in files:
            file_path = os.path.join(root_path, file)

            if not os.path.exists(file_path):
                raise Exception(f'{file} not exists in root folder.')
            copy(file_path, sdk_path)

        if not os.path.exists(os.path.join(root_path, 'sdk.toml')):
            raise Exception('sdk.toml not exists in root folder.')
        copyfile(os.path.join(root_path, 'sdk.toml'), os.path.join(sdk_path, 'pyproject.toml'))

    def generate_code(self, name: str, source_path: str, routes: list[BaseRoute]):
        microservices = list()
        for route in routes:
            if isinstance(route, APIRoute):
                if route.path.startswith('/microservices/'):
                    microservices.append(self.get_api_detail(route))

        microservice = group_by(microservices, 'class_name')
        self.create_file(name, source_path, microservice)

    def get_api_detail(self, route: APIRoute):
        qualified_name: str = route.endpoint.__qualname__
        parameters_position = list(route.endpoint.__code__.co_varnames)
        parameters_position.remove('self')

        if len(route.dependant.body_params) > 1:
            raise Exception('Cannot use more than 1 dto for microservices.')

        method_detail = MethodDetail(
            path=route.path,
            class_name=qualified_name.split('.')[0],
            function_name=qualified_name.split('.')[1],
            query_params=route.dependant.query_params,
            body_params=route.dependant.body_params,
            parameters_position=parameters_position,
            response_field=route.response_field,
        )
        return method_detail

    def create_file(self, name: str, source_path: str, microservice: Dict):
        sdk_template = SdkTemplate()
        content = ''''''
        content += sdk_template.generate_ref_class(microservice)
        for key in microservice.keys():
            content += sdk_template.generate_class(key, microservice[key])
        with open(getsourcefile(BaseMicroserviceClient), 'r') as file:
            content += file.read()
        content = content.replace(
            'ReplaceMicroserviceConfig', f'{name.replace("_", " ").replace("-", " ").title().replace(" ", "")}Config'
        )
        file_content = ssort(content)
        with open(os.path.join(source_path, 'sdk.py'), "w+") as file:
            file.write(file_content)
