import errno
import os
import random
import requests
import threading
import typing
import time
from datetime import datetime
from urllib import parse

from google.protobuf.timestamp_pb2 import Timestamp
from yc_as_client.exceptions import PermissionDeniedException, UnauthenticatedException, UnimplementedException, \
    YCAccessServiceException

import grpc
from load.projects.cloud.cloud_helper import iam
from load.projects.cloud.loadtesting import config
from load.projects.cloud.loadtesting.server.api.common import permissions

from prometheus_client import push_to_gateway

_BASE32 = '0123456789abcdefghjkmnpqrstvwxyz'
WAITING_TIMEOUT = 5  # seconds


def generate_cloud_id(size=20, base=_BASE32):
    '''
    https://clubs.at.yandex-team.ru/ycp/324
    https://bb.yandex-team.ru/projects/CLOUD/repos/cloud-go/browse/private-api/endpoint_ids
    '''
    salt = ''.join(random.choice(base) for _ in range(size - len(config.ENV_CONFIG.CLUSTER_ID)))
    return config.ENV_CONFIG.CLUSTER_ID + salt


def user_iam_token(metadata) -> typing.Optional[str]:
    bearer_prefix = 'Bearer '
    for k, v in metadata:
        if k == 'authorization' and v.startswith(bearer_prefix):
            return v[len(bearer_prefix):]
    return


def authorize(context, iam_token, folder_id, permission, request_id):
    try:
        return iam.IAM.authorize(folder_id, permission, iam_token, request_id)
    except PermissionDeniedException:
        context.abort(grpc.StatusCode.PERMISSION_DENIED, f'Permission {permission} denied')
    except UnauthenticatedException:
        context.abort(grpc.StatusCode.UNAUTHENTICATED, 'Authentication failed')
    except UnimplementedException:
        context.abort(grpc.StatusCode.INTERNAL, 'Failed to check permissions.')
    except YCAccessServiceException:
        context.abort(grpc.StatusCode.UNAVAILABLE, 'Failed to check permissions.')


def ts_from_dt(ts_field: typing.Union[datetime, None]) -> typing.Union[Timestamp, None]:
    ts_message = Timestamp()
    if ts_field is not None:
        ts_message.FromDatetime(ts_field)
    else:
        ts_message = None
    return ts_message


def fd_count(pid='self'):
    try:
        names = os.listdir(f"/proc/{pid}/fd")
        return len(names) - 1
    except OSError as exc:
        if exc.errno != errno.ENOENT:
            raise


def parse_target(raw_target):
    addr = parse.urlsplit('//' + raw_target)
    return addr.hostname, addr.port


def waiting_for_operation(db, db_operation, get_operation_func, foreign_operation, user_token, request_id):
    while not foreign_operation.done:  # TODO add limit
        time.sleep(WAITING_TIMEOUT)
        foreign_operation = get_operation_func(foreign_operation.id, user_token, request_id)
        db.operation.update(
            db_operation,
            error=foreign_operation.error.message,
            modified_at=foreign_operation.modified_at.ToDatetime(),
            done=foreign_operation.done)
    return foreign_operation


def validate_request_job(request, context, db, user_token, request_id):
    tank = db.tank.get_by_compute_instance_id(request.compute_instance_id)
    if not tank:
        raise context.abort(grpc.StatusCode.NOT_FOUND,
                            f'Compute instance {request.compute_instance_id} is not found.')

    authorize(context, user_token, tank.folder_id, permissions.TESTS_PUSHDATA, request_id)

    if request.job_id and tank.current_job != request.job_id:
        raise context.abort(grpc.StatusCode.INTERNAL, f'There is other current job for the agent {tank.id}')
    job = db.job.get(tank.current_job)
    if not job:
        raise context.abort(grpc.StatusCode.NOT_FOUND, f'There is no current job for the agent {tank.id}')
    return request, job


class PushMonitoringData:
    data_provider = None
    host = None
    logger = None
    instance_name = None
    last_action_time = time.time()
    frequency = 10  # seconds
    lock = threading.Lock()

    @classmethod
    def setup(cls, data_provider, host, logger, instance_name):
        cls.data_provider = data_provider
        cls.host = host
        cls.logger = logger
        cls.grouping_key = {'instance': instance_name}

    @classmethod
    def push(cls):
        if cls.lock.locked():
            return
        if time.time() - cls.last_action_time < cls.frequency:
            return

        t = threading.Thread(target=cls._push_blocking)
        t.start()
        return

    @classmethod
    def _push_blocking(cls):
        with cls.lock:
            try:
                push_to_gateway(cls.host, job='loadtesting-server', grouping_key=cls.grouping_key, registry=cls.data_provider)
            except requests.RequestException as e:
                cls.logger.exception(str(e))
