import os
import sys
import tempfile
import shutil
import random
import tarfile

from copy import deepcopy
from unittest import TestCase

from mock import Mock, patch
from base import ServerTests
import mock

from nectar.downloaders.local import LocalFileDownloader
from nectar.config import DownloaderConfig

sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)) + "/mocks")

from pulp_node.distributors.http.distributor import (NodesHttpDistributor,
                                                     entry_point as dist_entry_point)
from pulp_node.importers.http.importer import NodesHttpImporter, entry_point as imp_entry_point
from pulp_node.profilers.nodes import NodeProfiler, entry_point as profiler_entry_point
from pulp_node.handlers.handler import NodeHandler, RepositoryHandler

from pulp.devel import mock_config
from pulp.plugins.loader import api as plugin_api
from pulp.plugins.types import database as unit_db
from pulp.server import config as pulp_conf
from pulp.server.db import connection
from pulp.server.db import model
from pulp.server.db.model.repository import RepoDistributor, RepoImporter
from pulp.server.db.model.repository import RepoContentUnit
from pulp.server.db.model.consumer import Consumer, Bind
from pulp.server.db.model.content import ContentType
from pulp.plugins import model as plugin_model
from pulp.plugins.config import PluginCallConfiguration
from pulp.plugins.conduits.repo_publish import RepoPublishConduit
from pulp.plugins.conduits.repo_sync import RepoSyncConduit
from pulp.plugins.util.nectar_config import importer_config_to_nectar_config
from pulp.common.plugins import importer_constants
from pulp.common.config import Config
from pulp.server.managers import factory as managers
from pulp.server.content.sources.model import Request as DownloadRequest
from pulp.agent.lib.conduit import Conduit
from pulp_node.manifest import Manifest, RemoteManifest, MANIFEST_FILE_NAME, UNITS_FILE_NAME
from pulp_node.handlers.strategies import Mirror, Additive
from pulp_node import error
from pulp_node import constants
from pulp_node import pathlib


FAKE_DISTRIBUTOR = 'test_distributor'
FAKE_ID = 'fake_plugin_id'
FAKE_DISTRIBUTOR_CONFIG = {'A': 0}

NODE_CERTIFICATE = """
    -----BEGIN RSA PRIVATE KEY-----
    PULPROCKSPULPROCKSPULPROCKS
    -----END RSA PRIVATE KEY-----
    -----BEGIN CERTIFICATE-----
    PULPROCKSPULPROCKSPULPROCKS
    -----END CERTIFICATE-----
"""

REPO_NAME = 'pulp-nodes'
REPO_DESCRIPTION = 'full of goodness'
REPO_NOTES = {'the answer to everything': 42}
REPO_SCRATCHPAD = {'a': 1, 'b': 2}


class Repository(object):

    def __init__(self, repo_id, working_dir=None):
        self.id = repo_id
        self.working_dir = working_dir


class FakeDistributor(object):

    @classmethod
    def metadata(cls):
        return {
            'id':
 FAKE_DISTRIBUTOR,
            'display_name': 'Fake Distributor',
            'types': ['node']
        }

    def validate_config(self, *unused):
        return True, None

    def publish_repo(self, repo, conduit, config):
        return conduit.build_success_report('succeeded', {})

    def distributor_added(self, *unused):
        pass


class T
estStrategy:

    def __init__(self, tester, **options):
        self.tester = tester
        self.options = options

    def __call__(self):
        self.tester.clean(**self.options)
        return self._impl()()

    def _impl(self):
        raise NotImplementedError()


class MirrorTestStrategy(TestStrategy):

    def _impl(self):
        return Mirror


class AdditiveTestStrategy(TestStrategy):

    def _impl(self):
        return Additive


class BadDownloadRequest(DownloadRequest):

    def __init__(self, *args, **kwargs):
        DownloadRequest.__init__(self, *args, **kwargs)
        self.url = 'http:/NOWHERE/FAIL_ME_%f' % random.random()


class AgentConduit(Conduit):

    def __init__(self, node_id=None):
        self.node_id = node_id

    @property
    def consumer_id(self):
        return self.node_id


class PluginTestBase(ServerTests):

    REPO_ID = 'test-repo'
    UNIT_TYPE_ID = 'rpm'
    UNIT_ID = 'test_unit_%d'
    UNIT_KEY = {'A': 'a', 'B': 'b', 'N': 0}
    UNIT_METADATA = {'name': 'Elvis', 'age': 42}
    TYPEDEF_ID = UNIT_TYPE_ID
    NUM_UNITS = 10
    NUM_EXTRA_UNITS = 5
    EXTRA_REPO_IDS = ('extra_1', 'extra_2')

    PARENT_SETTINGS = {
        constants.HOST: 'pulp.redhat.com',
        constants.PORT: 443,
        constants.NODE_CERTIFICATE: NODE_CERTIFICATE,
    }

    @classmethod
    def tmpdir(cls, role):
        tmp_dir = tempfile.mkdtemp(dir=cls.TMP_ROOT, prefix=role)
        return tmp_dir

    def setUp(self):
        ServerTests.setUp(self)
        self.parentfs = self.tmpdir('parent-')
        self.childfs = self.tmpdir('child-')
        self.alias = (self.parentfs, self.parentfs)
        Consumer.get_collection().remove()
        Bind.get_collection().remove()
        RepoDistributor.get_collection().remove()
        RepoImporter.get_collection().remove()
        RepoContentUnit.get_collection().remove()
        unit_db.clean()
        self.define_plugins()
        plugin_api._create_manager()
        imp_conf = dict(strategy=constants.MIRROR_STRATEGY)
        plugin_api._MANAGER.importers.add_plugin(
            constants.HTTP_IMPORTER, NodesHttpImporter, imp_conf)
        plugin_api._MANAGER.distributors.add_plugin(
            constants.HTTP_DISTRIBUTOR, NodesHttpDistributor, {})
        plugin_api._MANAGER.distributors.add_plugin(
            FAKE_DISTRIBUTOR, FakeDistributor, FAKE_DISTRIBUTOR_CONFIG)
        plugin_api._MANAGER.profilers.add_plugin(constants.PROFILER_ID, NodeProfiler, {})

    def tearDown(self):
        ServerTests.tearDown(self)
        shutil.rmtree(self.parentfs)
        shutil.rmtree(self.childfs)
        Consumer.get_collection().remove()
        Bind.get_collection().remove()
        model.Repository.drop_collection()
        RepoDistributor.get_collection().remove()
        RepoImporter.get_collection().remove()
        RepoContentUnit.get_collection().remove()
        unit_db.clean()

    def define_plugins(self):
        collection = ContentType.get_collection()
        collection.save(dict(id=self.TYPEDEF_ID, unit_key=self.UNIT_KEY.keys()))

    @mock.patch('pulp.server.managers.repo.unit_association.repo_controller')
    def populate(self, mock_repo_ctrl):
        # make content/ dir.
        os.makedirs(os.path.join(self.parentfs, 'content'))

        with mock_config.patch({'server': {'storage_dir': self.parentfs}}):
            units = self.add_units(0, self.NUM_UNITS)
            self.units = units

    def node_configuration(self):
        path = os.path.join(self.parentfs, 'node.crt')
        with open(path, 'w+') as fp:
            fp.write(NODE_CERTIFICATE)
        node_conf = Config({'main': {constants.NODE_CERTIFICATE: path}})
        return node_conf.graph()

    def add_units(self, begin, end):
        units = []
        storage_dir = os.path.join(pulp_conf.config.get('server', 'storage_dir'), 'content')
        if not os.path.exists(storage_dir):
            os.makedirs(storage_dir)
        for n in range(begin, end):
            unit_id = self.UNIT_ID % n
            unit = dict(self.UNIT_KEY)
            unit.update(self.UNIT_METADATA)
            unit['N'] = n
            # add unit file
            storage_path = os.path.join(storage_dir, '.'.join((unit_id, self.UNIT_TYPE_ID)))
            if n % 2 == 0:  # even numbered has file associated
                unit['_storage_path'] = storage_path
                if n == 0:  # 1st one is a directory of files
                    os.makedirs(storage_path)
                    dist_path = os.path.join(os.path.dirname(__file__), 'data/distribution.tar')
                    tb = tarfile.open(dist_path)
                    tb.extractall(path=storage_path)
                    tb.close()
                else:
                    with open(storage_path, 'w+') as fp:
                        fp.write(unit_id)
            # add unit
            manager = managers.content_manager()
            manager.add_content_unit(
                self.UNIT_TYPE_ID,
                unit_id,
              