# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

import os
import os.path
import shutil
import tempfile
import inspect
import unittest

from nose.tools import eq_, ok_, assert_raises
from configman import ConfigurationManager
from mock import Mock

from socorro.external.crashstorage_base import CrashIDNotFound
from socorro.external.filesystem.crashstorage import (
  FileSystemRawCrashStorage,
  FileSystemThrottledCrashStorage,
  FileSystemCrashStorage)
from socorro.lib.util import DotDict


class TestFileSystemCrashStorage(unittest.TestCase):

    def setUp(self):
        self.std_tmp_dir = tempfile.mkdtemp()
        self.def_tmp_dir = tempfile.mkdtemp()
        self.pro_tmp_dir = tempfile.mkdtemp()

    def tearDown(self):
        shutil.rmtree(self.std_tmp_dir)

    @staticmethod
    def _get_class_methods(klass):
        return dict((n, ref) for (n, ref)
                    in inspect.getmembers(klass, inspect.ismethod)
                    if not n.startswith('_') and n in klass.__dict__)

    def _find_file(self, in_, filename):
        found = []
        for f in os.listdir(in_):
            path = os.path.join(in_, f)
            if os.path.isdir(path):
                found.extend(self._find_file(path, filename))
            elif os.path.isfile(path) and filename in path:
                found.append(path)
        return found

    def _common_config_setup(self):
        mock_logging = Mock()
        required_config = FileSystemCrashStorage.get_required_config()
        required_config.add_option('logger', default=mock_logging)
        config_manager = ConfigurationManager(
          [required_config],
          app_name='testapp',
          app_version='1.0',
          app_description='app description',
          values_source_list=[{
            'logger': mock_logging,
            'std_fs_root': self.std_tmp_dir,
            'def_fs_root': self.def_tmp_dir,
            'pro_fs_root': self.pro_tmp_dir,
          }],
          argv_source=[]
        )
        return config_manager

    def _common_basic_test(self, config, crashstorage):
        fake_dump = 'this is a fake dump'
        eq_(list(crashstorage.new_crashes()), [])
        raw = {"name": "Peter",
               "legacy_processing": 0,
               "submitted_timestamp": '2012-03-14 15:10:33'}
        crashstorage.save_raw_crash(
          raw,
          fake_dump,
          "114559a5-d8e6-428c-8b88-1c1f22120314"
        )

        fake_dumps = {None: 'this is a fake dump', 'aux01': 'aux01 fake dump'}
        raw = {"name": "Lars",
               "legacy_processing": 0,
               "submitted_timestamp": '2012-05-04 15:10:33'}
        crashstorage.save_raw_crash(
          raw,
          fake_dumps,
          "114559a5-d8e6-428c-8b88-1c1f22120504"
        )
        eq_(sorted(list(crashstorage.new_crashes())),
                         sorted(["114559a5-d8e6-428c-8b88-1c1f22120314",
                          "114559a5-d8e6-428c-8b88-1c1f22120504",
                         ]))

        ok_(
          os.path.exists(
            crashstorage.std_crash_store.getJson(
                '114559a5-d8e6-428c-8b88-1c1f22120314')))
        ok_(
          os.path.exists(
            crashstorage.std_crash_store.getDump(
                '114559a5-d8e6-428c-8b88-1c1f22120314')))
        ok_(
          os.path.exists(
            crashstorage.std_crash_store.getJson(
                '114559a5-d8e6-428c-8b88-1c1f22120504')))
        ok_(
          os.path.exists(
            crashstorage.std_crash_store.getDump(
                '114559a5-d8e6-428c-8b88-1c1f22120504')))

        meta = crashstorage.get_raw_crash(
          '114559a5-d8e6-428c-8b88-1c1f22120314')
        ok_(isinstance(meta, DotDict))
        eq_(meta['name'], 'Peter')

        dump = crashstorage.get_raw_dump(
          '114559a5-d8e6-428c-8b88-1c1f22120314')
        ok_(isinstance(dump, basestring))
        ok_("fake dump" in dump)

        dumps = crashstorage.get_raw_dumps(
          '114559a5-d8e6-428c-8b88-1c1f22120504'
        )
        eq_(['upload_file_minidump', 'aux01'], dumps.keys())
        eq_(['this is a fake dump', 'aux01 fake dump'],
            dumps.values())

        crashstorage.remove('114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                      crashstorage.std_crash_store.getJson,
                      '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                      crashstorage.std_crash_store.getDump,
                      '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(CrashIDNotFound,
                      crashstorage.get_raw_crash,
                      '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(CrashIDNotFound,
                      crashstorage.get_raw_dump,
                      '114559a5-d8e6-428c-8b88-1c1f22120314')

    def _common_throttle_test(self, config, crashstorage):
        fake_dump = 'this is a fake dump'
        crashstorage = FileSystemThrottledCrashStorage(config)
        eq_(list(crashstorage.new_crashes()), [])
        raw = {"name": "Peter",
               "legacy_processing": 1,
               "submitted_timestamp": '2012-05-04 15:10:33'}
        crashstorage.save_raw_crash(
          raw,
          fake_dump,
          "114559a5-d8e6-428c-8b88-1c1f22120314"
        )

        fake_dumps = {None: 'this is a fake dump', 'aux01': 'aux01 fake dump'}
        raw = {"name": "Lars",
               "legacy_processing": 0,
               "submitted_timestamp": '2012-05-04 15:10:33'}
        crashstorage.save_raw_crash(
          raw,
          fake_dumps,
          "114559a5-d8e6-428c-8b88-1c1f22120504"
        )
        eq_(list(crashstorage.new_crashes()),
                         ["114559a5-d8e6-428c-8b88-1c1f22120504",])

        ok_(
          os.path.exists(
            crashstorage.def_crash_store.getJson(
                '114559a5-d8e6-428c-8b88-1c1f22120314')))
        ok_(
          os.path.exists(
            crashstorage.def_crash_store.getDump(
                '114559a5-d8e6-428c-8b88-1c1f22120314')))
        assert_raises(OSError,
                          crashstorage.std_crash_store.getJson,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                          crashstorage.std_crash_store.getDump,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')

        meta = crashstorage.get_raw_crash(
          '114559a5-d8e6-428c-8b88-1c1f22120314')
        ok_(isinstance(meta, DotDict))
        eq_(meta['name'], 'Peter')

        dump = crashstorage.get_raw_dump(
          '114559a5-d8e6-428c-8b88-1c1f22120314')
        ok_(isinstance(dump, basestring))
        ok_("fake dump" in dump)

        crashstorage.remove('114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                          crashstorage.def_crash_store.getJson,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                          crashstorage.def_crash_store.getDump,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                          crashstorage.std_crash_store.getJson,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')
        assert_raises(OSError,
                          crashstorage.std_crash_store.getDump,
                          '114559a5-d8e6-428c-8b88-1c1f22120314')

    def test_filesystem_raw_crashstorage(self):
        config_manager = self._common_config_setup()
        with config_manager.context() as config:
            crashstorage = FileSystemRawCrashStorage(config)
            self._common_basic_test(config, crashstorage)

    def test_filesystem_throttled_crashstorage(self):
        config_manager = self._common_config_setup()
        with config_manager.context() as config:
            crashstorage = FileSystemThrottledCrashStorage(config)
            self._common_basic_test(config, crashstorage)
            self._common_throttle_test(config, crashstorage)

    def test_filesystem_crashstorage(self):
        config_manager = self._common_config_setup()
        with config_manager.context() as config:
            crashstorage = FileSystemCrashStorage(config)
            self._common_throttle_test(config, crashstorage)

            crashstorage = FileSystemCrashStorage(config)
            eq_(list(crashstorage.new_crashes()), [])

            processed_crash = {"name": "Peter", "legacy_processing": 1}
            assert_raises(
              CrashIDNotFound,
              crashstorage.save_processed,
              processed_crash
            )
            processed_crash = {
                "name": "Peter",
                "uuid": "114559a5-d8e6-428c-8b88-1c1f22120314",
                "email": "lars@nowhere.org",
            }
            expected_processed_crash = {
                "name": "Peter",
                "uuid": "114559a5-d8e6-428c-8b88-1c1f22120314",
            }
            crash_id = processed_crash['uuid']
            crashstorage.save_processed(processed_crash)
            returned_processed_crash = crashstorage.get_processed(crash_id)
            eq_(
                expected_processed_crash,
                returned_processed_crash
            )
            ok_(isinstance(returned_processed_crash,
                                       DotDict))
            returned_processed_crash = \
                crashstorage.get_unredacted_processed(crash_id)
            eq_(
                processed_crash,
                returned_processed_crash
            )

            crashstorage.remove(crash_id)
            assert_raises(CrashIDNotFound,
                              crashstorage.get_processed,
                              crash_id)
            crashstorage.remove(crash_id)
