import functools
import logging
import requests
import time
from collections.abc import Iterable

from retry import retry

from ci.tasklet.common.proto import service_pb2 as ci
from tasklet.services.yav.proto import yav_pb2 as yav

from load.projects.cloud.loadtesting.tasklets.teamcity.proto import run_build_pb2, run_build_tasklet

logger = logging.getLogger(__name__)

TEAMCITY_TOKEN = 'teamcity.aw.cloud.token'


class RunBuildException(Exception):
    def __init__(self, message):
        self.message = message

    def __str__(self):
        return f'Run build TeamCity script failed with exception {self.message}'


class RunBuildImpl(run_build_tasklet.RunBuildBase):
    def __init__(self, *args, **kwargs):
        super(RunBuildImpl, self).__init__(*args, **kwargs)
        self.build_state = None
        self.build_status = None

    @functools.cached_property
    def secret(self):
        spec = yav.YavSecretSpec(uuid=self.input.secret.secret_uid, key=TEAMCITY_TOKEN)
        return self.ctx.yav.get_secret(spec).secret

    @retry(tries=5, delay=1, backoff=2)
    def request(self, url, data=None):
        headers = {
            'Authorization': f'Bearer {self.secret}',
            'Accept': 'application/json',
        }
        if data:
            response = requests.post(url, headers=headers, json=data)
        else:
            response = requests.get(url, headers=headers)
        logger.debug(f'Response info is {response.request.method} {response.status_code} {response.url}')
        response.raise_for_status()
        return response

    def run(self):
        progress = ci.TaskletProgress()
        progress.job_instance_id.CopyFrom(self.input.context.job_instance_id)
        progress.progress = 0
        progress.module = 'TEAMCITY'
        self.ctx.ci.UpdateProgress(progress)

        teamcity_api_url = self.input.teamcity_api.url

        build_type_id = self.input.build_type.id
        props = [{'name': property_name, 'value': property_value}
                 for property_name, property_value in self.input.build_type.properties.items()]
        data = {'buildType': {'id': build_type_id}, 'properties': {'property': props}}
        if self.input.dependency.id > 0:
            locator = f'id:{self.input.dependency.id}'
            try:
                build = self.request(f'{teamcity_api_url}/app/rest/builds/?locator={locator}').json()['build']
            except (ValueError, KeyError) as exc:
                raise RunBuildException('Build was not found') from exc
            logger.debug(f'Found build is {build}')
            data['snapshot-dependencies'] = {'build': build}

        try:
            build = self.request(f'{teamcity_api_url}/app/rest/buildQueue', data=data).json()
            build_url = build['webUrl']
            build_href = build['href']
        except (ValueError, KeyError) as exc:
            raise RunBuildException('Teamcity build was not started') from exc

        progress.status = ci.TaskletProgress.Status.RUNNING
        progress.url = build_url
        progress.text = 'Build in queue'
        self.ctx.ci.UpdateProgress(progress)

        while self.build_state is None or self.build_state in ('queued', 'running'):
            build = self.request(teamcity_api_url + build_href).json()
            build_id = build['id']
            self.build_state = build['state']
            logger.debug(f'Build state is {self.build_state}')
            if self.build_state == 'queued':
                time.sleep(30)
                continue
            self.build_status = build['status']
            build_status_text = build['statusText']
            if self.build_state in 'running':
                try:
                    progress.progress = build['percentageComplete'] / 100
                except KeyError:
                    logger.debug('There is no percentageComplete in response')
                progress.text = build_status_text
                self.ctx.ci.UpdateProgress(progress)
            else:
                self.output.build.id = build_id
                self.output.build.url = build_url
                artifacts_url = f'{teamcity_api_url}/app/rest/builds/id:{build_id}/artifacts'
                try:
                    artifacts = self.request(artifacts_url).json()['file']
                except (ValueError, KeyError) as exc:
                    raise RunBuildException('No artifacts') from exc
                if not isinstance(artifacts, Iterable):
                    raise RunBuildException('Artifacts are not iterable')
                self.output.build_artifacts.extend(
                    run_build_pb2.BuildArtifact(
                        name=artifact['name'],
                        url=f'{teamcity_api_url}/repository/download/{build_type_id}/{build_id}:id/{artifact["name"]}',
                    )
                    for artifact in artifacts
                )
                progress.progress = 1
                progress.text = build_status_text
                progress.status = ci.TaskletProgress.Status.SUCCESSFUL \
                    if self.build_status == 'SUCCESS' else ci.TaskletProgress.Status.FAILED
                self.ctx.ci.UpdateProgress(progress)
                if self.build_status != 'SUCCESS':
                    raise RunBuildException('Teamcity build failed')
            time.sleep(30)
