import datetime
import json
import unittest
import urllib.request

from unittest.mock import patch
from urllib.error import HTTPError, URLError

from django import template
from django.core.exceptions import ValidationError
from django.test import TestCase, override_settings
from django.urls import reverse
from django.utils.timezone import make_aware, now

from wagtail.core import blocks
from wagtail.embeds import oembed_providers
from wagtail.embeds.blocks import EmbedBlock, EmbedValue
from wagtail.embeds.embeds import get_embed, get_embed_hash
from wagtail.embeds.exceptions import EmbedNotFoundException, EmbedUnsupportedProviderException
from wagtail.embeds.finders import get_finders
from wagtail.embeds.finders.embedly import AccessDeniedEmbedlyException, EmbedlyException
from wagtail.embeds.finders.embedly import EmbedlyFinder as EmbedlyFinder
from wagtail.embeds.finders.facebook import AccessDeniedFacebookOEmbedException
from wagtail.embeds.finders.facebook import FacebookOEmbedFinder as FacebookOEmbedFinder
from wagtail.embeds.finders.instagram import AccessDeniedInstagramOEmbedException
from wagtail.embeds.finders.instagram import InstagramOEmbedFinder as InstagramOEmbedFinder
from wagtail.embeds.finders.oembed import OEmbedFinder as OEmbedFinder
from wagtail.embeds.models import Embed
from wagtail.embeds.templatetags.wagtailembeds_tags import embed_tag
from wagtail.tests.utils import WagtailTestUtils


try:
    import embedly  # noqa
    no_embedly = False
except ImportError:
    no_embedly = True


class TestGetFinders(TestCase):
    def test_defaults_to_oembed(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], OEmbedFinder)

    # New WAGTAILEMBEDS_FINDERS setting

    @override_settings(WAGTAILEMBEDS_FINDERS=[
        {
            'class': 'wagtail.embeds.finders.oembed'
        }
    ])
    def test_new_find_oembed(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], OEmbedFinder)

    @override_settings(WAGTAILEMBEDS_FINDERS=[
        {
            'class': 'wagtail.embeds.finders.embedly',
            'key': 'foo',
        }
    ])
    def test_new_find_embedly(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], EmbedlyFinder)
        self.assertEqual(finders[0].get_key(), 'foo')

    @override_settings(WAGTAILEMBEDS_FINDERS=[
        {
            'class': 'wagtail.embeds.finders.oembed',
            'options': {'foo': 'bar'}
        }
    ])
    def test_new_find_oembed_with_options(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], OEmbedFinder)
        self.assertEqual(finders[0].options, {'foo': 'bar'})

    @override_settings(WAGTAILEMBEDS_FINDERS=[
        {
            'class': 'wagtail.embeds.finders.instagram',
            'app_id': '1234567890',
            'app_secret': 'abcdefghijklmnop',
        },
    ])
    def test_find_instagram_oembed_with_options(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], InstagramOEmbedFinder)
        self.assertEqual(finders[0].app_id, '1234567890')
        self.assertEqual(finders[0].app_secret, 'abcdefghijklmnop')
        # omitscript defaults to False
        self.assertIs(finders[0].omitscript, False)

    @override_settings(WAGTAILEMBEDS_FINDERS=[
        {
            'class': 'wagtail.embeds.finders.facebook',
            'app_id': '1234567890',
            'app_secret': 'abcdefghijklmnop',
        },
    ])
    def test_find_facebook_oembed_with_options(self):
        finders = get_finders()

        self.assertEqual(len(finders), 1)
        self.assertIsInstance(finders[0], FacebookOEmbedFinder)
        self.assertEqual(finders[0].app_id, '1234567890')
        self.assertEqual(finders[0].app_secret, 'abcdefghijklmnop')
        # omitscript defaults to False
        self.assertIs(finders[0].omitscript, False)


class TestEmbeds(TestCase):
    def setUp(self):
        self.hit_count = 0

    def dummy_finder(self, url, max_width=None, max_height=None):
        # Up hit count
        self.hit_count += 1

        # Return a pretend record
        return {
            'title': "Test: " + url,
            'type': 'video',
            'width': max_width if max_width else 640,
            'height': 480,
            'html': "<p>Blah blah blah</p>",
        }

    @override_settings(WAGTAILEMBEDS_RESPONSIVE_HTML=True)
    def test_get_embed_responsive(self):
        embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder)

        # Check that the embed is correct
        self.assertEqual(embed.title, "Test: www.test.com/1234")
        self.assertEqual(embe
d.type, 'video')
        self.assertEqual(embed.width, 400)
        self.assertEqual(embed.thumbnail_url, '')

        # Check ratio calculations
        self.assertEqual(embed.ratio, 480 / 400)
        self.assertEqual(embed.ratio_css, '120.0%')
        self.assertTrue(embed.is_responsive)

        # Check that there has only been one hit to the backend
        self.assertEqual(self.hi
t_count, 1)

        # Look for the same embed again and check the hit count hasn't increased
        embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder)
        self.assertEqual(self.hit_count, 1)

        # Look for a different embed, hit count should increase
        embed = get_embed('www.test.com/4321', max_width=400, finder=self.dummy_finder)
        self.assertEqual(self.hit_count, 2)

        # Look for the same embed with a different width, this should also increase hit count
        embed = get_embed('www.test.com/4321', finder=self.dummy_finder)
        self.assertEqual(self.hit_count, 3)

    def test_get_embed_nonresponsive(self):
        embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_finder)

        # Check that the embed is correct
        self.assertEqual(embed.title, "Test: www.test.com/1234")
        self.assertEqual(embed.type, 'video')
        self.assertEqual(embed.width, 400)
        self.assertFalse(embed.is_responsive)
        self.assertIsNone(embed.cache_until)

    def dummy_cache_until_finder(self, url, max_width=None, max_height=None):
        # Up hit count
        self.hit_count += 1

        # Return a pretend record
        return {
            'title': "Test: " + url,
            'type': 'video',
            'width': max_width if max_width else 640,
            'height': 480,
            'html': "<p>Blah blah blah</p>",
            'cache_until': make_aware(datetime.datetime(2001, 2, 3)),
        }

    def test_get_embed_cache_until(self):
        embed = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_cache_until_finder)
        self.assertEqual(embed.cache_until, make_aware(datetime.datetime(2001, 2, 3)))
        self.assertEqual(self.hit_count, 1)

        # expired cache_until should be ignored
        embed_2 = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_cache_until_finder)
        self.assertEqual(self.hit_count, 2)

        # future cache_until should not be ignored
        future_dt = now() + datetime.timedelta(minutes=1)
        embed.cache_until = future_dt
        embed.save()
        embed_3 = get_embed('www.test.com/1234', max_width=400, finder=self.dummy_cache_until_finder)
        self.assertEqual(self.hit_count, 2)

        # ensure we've received the same embed
        self.assertEqual(embed, embed_2)
        self.assertEqual(embed, embed_3)
        self.assertEqual(embed_3.cache_until, future_dt)

    def dummy_finder_invalid_width(self, url, max_width=None, max_height=None):
        # Return a record with an invalid width
        return {
            'title': "Test: " + url,
            'type': 'video',
            'thumbnail_url': '',
            'width': '100%',
            'height': 480,
            'html': "<p>Blah blah blah</p>",
        }

    def test_invalid_width(self):
        embed = get_embed('www.test