#!/usr/bin/env python
# -*- coding: utf-8 -*-


import os
import re
import sys
from pathlib import Path
from enum import Enum

from . import settings
from . import utils
from . import exceptions
from . import pygittools
from . import wizard
from . import logger
from . import reltools


_logger = logger.get_logger(__name__)

_VERSION_REGEX = r"__version__ *= *['|\"]\S+"


class ReleaseAction(Enum):
    MAKE_RELEASE = 'rel'
    REGENERATE = 'reg'


def make_install(options=None, cwd='.'):
    _logger.info('Performing installation...')
    
    if not options or (options and options.force != 'force'):
        reltools.check_repo_tree(cwd)
        reltools.check_if_changes_to_commit(cwd)
    
    try:
        release_tag = pygittools.get_latest_tag(cwd)
    except pygittools.PygittoolsError as e:
        raise exceptions.ReleaseMetadataError(f"Retrieving release tag error: {e}", _logger)

    final_release_tag = _get_final_release_tag(release_tag, cwd)
    
    _run_setup_cmd(['install'], release_tag=final_release_tag, cwd=cwd)
    
    _logger.info('Installation completed.')
    

def make_release(action=ReleaseAction.REGENERATE, prompt=True, push=True, release_data=None, options=None, cwd='.'):
    _logger.info('Preparing Source Distribution...')
    
    if not options or (options and options.force != 'force'):
        reltools.check_repo_tree(cwd)
        reltools.check_if_changes_to_commit(cwd)
    
    release_files_paths = []
    config = utils.get_repo_config_from_setup_cfg(Path(cwd) / settings.FileName.SETUP_CFG)

    if prompt:
        action = _release_checkout(config)
        if action == ReleaseAction.MAKE_RELEASE:
            new_release_tag = reltools.prompt_release_tag(reltools.TagType.PYTHON, cwd)
            new_release_msg = reltools.prompt_release_msg(cwd)
    else:
        if action == ReleaseAction.MAKE_RELEASE:
            new_release_tag = release_data.tag
            new_release_msg = release_data.msg
            
    if action == ReleaseAction.MAKE_RELEASE:
        files_to_add = [_update_project_version(config, new_release_tag, cwd)]
        changelog_type = _get_reltools_changelog_type(config)
        changelog_generated_template_path = (Path(__file__).parent / settings.DirName.TEMPLATES 
                                             / f'{settings.FileName.CHANGELOG_GENERATED}{settings.JINJA2_TEMPLATE_EXT}')
        changelog_prepared_template_path = (Path(__file__).parent / settings.DirName.TEMPLATES 
                                            / f'{settings.FileName.CHANGELOG_PREPARED}{settings.JINJA2_TEMPLATE_EXT}')
        authors_type = _get_reltools_authors_type(config)
        authors_generated_template_path = (Path(__file__).parent / settings.DirName.TEMPLATES 
                                             / f'{settings.FileName.AUTHORS_GENERATED}{settings.JINJA2_TEMPLATE_EXT}')
        authors_prepared_template_path = (Path(__file__).parent / settings.DirName.TEMPLATES 
                                            / f'{settings.FileName.AUTHORS_PREPARED}{settings.JINJA2_TEMPLATE_EXT}')
        
        files_to_add.append(reltools.update_changelog(changelog_type, 
                                                      settings.FileName.CHANGELOG, 
                                                      config.__dict__, 
                                                      new_release_tag, 
                                                      new_release_msg, 
                                                      changelog_generated_template_path=changelog_generated_template_path, 
                                                      changelog_prepared_template_path=changelog_prepared_template_path, 
                                                      cwd=cwd))
        files_to_add.append(reltools.update_authors(authors_type, 
                                                    settings.FileName.AUTHORS, 
                                                    config.__dict__, 
                                                    authors_generated_template_path, 
                                                    authors_prepared_template_path, 
                                                    cwd))

        release_files_paths.extend(reltools.commit_and_push_release_update(new_release_tag, 
                                                                           new_release_msg, 
                                                                           files_to_add=files_to_add, 
                                                                           push=push, 
                                                                           cwd=cwd,
                                                                           prompt=prompt))
        release_tag = new_release_tag
        
    elif action == ReleaseAction.REGENERATE:
        try:
            release_tag = pygittools.get_latest_tag(cwd)
        except pygittools.PygittoolsError as e:
            raise exceptions.ReleaseMetadataError(f"Retrieving release tag error: {e}"
                                                  f'Repository must be tagged before regenerate.', _logger)

    final_release_tag = _get_final_release_tag(release_tag, cwd, action)
    _run_setup_cmd(['sdist', 'bdist_wheel'], release_tag=final_release_tag, cwd=cwd)
    
    package_path = utils.get_latest_tarball(Path(cwd) / settings.DirName.DISTRIBUTION)
    
    if final_release_tag and final_release_tag not in package_path.name:
        raise exceptions.RuntimeError('Source Distribution preparing error! '
                                      'Sdidt package name not valid. Please try again.', _logger) 
    
    _logger.info(f'Source Distribution {utils.get_rel_path(package_path, cwd)} prepared properly.')
    
    return package_path


def _run_setup_cmd(cmd, release_tag=None, cwd='.'):
    setup_path = Path(cwd).resolve() / settings.FileName.SETUP_PY
    if not setup_path.exists():
        raise exceptions.FileNotFoundError(f'{utils.get_rel_path(setup_path, cwd)} '
                                           f'file not found that is necessary to the distribution process!', _logger)

    if release_tag:
        os.environ['PBR_VERSION'] = release_tag
    else:
        _logger.info('Release tag will be set by pbr automatically.')
    result = utils.execute_cmd([sys.executable, setup_path.__str__()] + cmd, cwd)
    for line in result.splitlines():
        _logger.info(line)
        
    
def _get_final_release_tag(release_tag, cwd, action=None):
    if not action or (action == ReleaseAction.REGENERATE):
        try:
            tag_commit_hash = pygittools.get_tag_commit_hash(release_tag, cwd)
        except pygittools.PygittoolsError as e:
            raise exceptions.ReleaseMetadataError(f'Retrieving tag commit hash error: {e}', _logger)
        
        try:
            latest_commit_hash = pygittools.get_latest_commit_hash(cwd)
        except pygittools.PygittoolsError as e:
            raise exceptions.ReleaseMetadataError(f'Retrieving latest commit hash error: {e}', _logger)
            
        if tag_commit_hash == latest_commit_hash:
            return release_tag
        else:
            return None
    elif action == ReleaseAction.MAKE_RELEASE:
        return release_tag
        

def _release_checkout(config):
    action = wizard.choose_one(__name__, 
                               'Make Release or Regenerate a release package using the actual release metadata',
                               choices=[ReleaseAction.MAKE_RELEASE.value, ReleaseAction.REGENERATE.value])
    action = ReleaseAction.MAKE_RELEASE if action == ReleaseAction.MAKE_RELEASE.value else ReleaseAction.REGENERATE
    
    if action == ReleaseAction.MAKE_RELEASE:
        if not wizard.is_checkpoint_ok(__name__, 'Are you on the relevant branch?'):
            raise exceptions.ReleaseCheckoutError('Checkout to the proper branch!', _logger)
        if not wizard.is_checkpoint_ok(__name__, 'Are there any uncommited changes or files not '
                                       'added into the repo tree?', valid_value='n'):
            raise exceptions.ReleaseCheckoutError('Commit your changes!', _logger)
        if not wizard.is_checkpoint_ok(__name__, f'Is the {settings.FileName.README} file prepared correctly?'):
            raise exceptions.ReleaseCheckoutError(f'Complete {settings.FileName.README} file!', _logger)
        if not wizard.is_checkpoint_ok(__name__, 
                                       f'Is there something that should be added to {settings.FileName.TODO} file?', 
                                       valid_value='n'):
            raise exceptions.ReleaseCheckoutError(f'Complete {settings.FileName.TODO} file!', _logger)
        if config.changelog_type == settings.ChangelogType.PREPARED.value:
            if not wizard.is_checkpoint_ok(__name__, f'Is the {settings.FileName.CHANGELOG} file up to date?'):
                raise exceptions.ReleaseCheckoutError(f'Complete {settings.FileName.CHANGELOG} file!', _logger)
        
    return action


def _update_project_version(config, release_tag, cwd='.'):
    if config.project_type == settings.ProjectType.MODULE.value:
        project_module_name = utils.get_module_name_with_suffix(config.project_name)
        file_version_path = Path(cwd).resolve() / project_module_name
        _update_version(file_version_path, release_tag, cwd)
    else:
        file_version_path = Path(cwd).resolve() / config.project_name / settings.FileName.PYINIT
        _update_version(file_version_path, release_tag, cwd)
        
    return file_version_path


def _update_version(file_version_path, release_tag, cwd='.'):
    new_version_string = f"__version__ = '{release_tag}'"
    try:
        with open(file_version_path, 'r+t') as file:
            content = file.read()
            if not re.search(_VERSION_REGEX, content):
                raise exceptions.VersionNotFoundError(f'__version__ variable not found in the '
                                                      f'{utils.get_rel_path(file_version_path, cwd)} file. '
                                                      f'Please correct the file.', _logger)
            else:
                updated_content = re.sub(_VERSION_REGEX, new_version_string, content, 1)
                file.seek(0)
                file.truncate()
                file.write(updated_content)
    except FileNotFoundError:
        raise exceptions.FileNotFoundError(f'File {utils.get_rel_path(file_version_path, cwd)} with a __version__ '
                                           f'variable not foud. File with the __version__ '
                                           f'variable is searched using the project_name entry in '
                                           f'{settings.FileName.SETUP_CFG}', _logger)


def _get_reltools_changelog_type(config):
    if config.changelog_type == settings.ChangelogType.GENERATED.value:
        return reltools.ChangelogType.GENERATED
    else:
        return reltools.ChangelogType.PREPARED


def _get_reltools_authors_type(config):
    if config.authors_type == settings.AuthorsType.GENERATED.value:
        return reltools.AuthorsType.GENERATED
    else:
        return  reltools.AuthorsType.PREPARED
