from files.helpers.config.const import RENDER_DEPTH_LIMIT
from . import fixture_accounts
from . import fixture_submissions
from . import fixture_comments
from . import util
from flask import g
from files.__main__ import app, db_session
from files.classes import Submission, Comment, User
from files.classes.visstate import StateMod
from files.helpers.comments import bulk_recompute_descendant_counts
import json
import random


def assert_comment_visibility(post, comment_body, clients):
	for client_name, (client, should_see) in clients.items():
		response = client.get(f'/post/{post.id}')
		if should_see:
			assert comment_body in response.text, f'{client_name} should see comment'
		else:
			assert comment_body not in response.text, f'{client_name} should not see comment'

@util.no_rate_limit
def test_submission_comment_count(accounts, submissions, comments):
	"""
	Scenario:
		1. There is a submission
		2. Bob the badpoaster poasts a comment on the submission
		3. submission.comment_count goes up by 1. Everyone can see the comment.
		4. Alice the admin removes the comment
		5. submission.comment_count goes down by 1. Only Bob and admins can see the comment.
	"""
	db = db_session()
	alice_client, alice = accounts.client_and_user_for_account('Alice')
	alice.admin_level = 2
	db.add(alice)
	db.commit()

	bob_client, bob = accounts.client_and_user_for_account('Bob')
	carol_client, carol = accounts.client_and_user_for_account('Carol')
	logged_off_client = accounts.logged_off()
	
	post = submissions.submission_for_client(alice_client, {
		'title': 'Weekly Takes',
		'body': 'Post your takes. Bad takes will be removed',
	})
	post_id = post.id

	post = db.query(Submission).filter_by(id=post_id).first()
	assert 0 == post.comment_count

	comment_body = 'The sun is a social construct.'
	comment = comments.comment_for_client(bob_client, post.id, {
		'body': comment_body,
	})

	post = db.query(Submission).filter_by(id=post_id).first()
	assert 1 == post.comment_count

	assert_comment_visibility(post, comment_body, {
		'alice': (alice_client, True),
		'bob': (bob_client, True),
		'carol': (carol_client, True),
		'guest': (logged_off_client, True),
	})

	alice_formkey = util.formkey_from(alice_client.get(f'/post/{post.id}').text)
	response = alice_client.post(
		'/admin/update_filter_status',
		data=json.dumps({
			'comment_id': comment.id,
			'new_status': 'removed',
			"formkey": alice_formkey,
		}),
		content_type='application/json'
	)
	assert 200 == response.status_code

	post = db.query(Submission).filter_by(id=post_id).first()

	assert_comment_visibility(post, comment_body, {
		# Alice should see the comment because she is an admin, level >= 2
		'alice': (alice_client, True),
		# Bob should see the comment because he wrote the comment
		'bob': (bob_client, True),
		# Other users, and guests, should NOT see the comment, since it has been removed
		'carol': (carol_client, False),
		'guest': (logged_off_client, False),
	})

	assert 0 == post.comment_count

@util.no_rate_limit
def test_comment_descendant_count(accounts, submissions, comments):
	"""
		Here is a contentious top-level comment
			You're wrong, this isn't contentious
				no u
			Good poast
	"""
	db = db_session()
	alice_client, alice = accounts.client_and_user_for_account('Alice')

	post = submissions.submission_for_client(alice_client, {
		'title': 'Discussion',
		'body': 'Discuss stuff',
	})
	post_id = post.id

	root = comments.comment_for_client(alice_client, post.id, {
		'body': 'Here is a contentious top-level comment',
	})

	assert 0 == db.query(Comment).filter_by(id=root.id).first().descendant_count

	reply1 = comments.comment_for_client(alice_client, post.id, {
		'body': 'You\'re wrong, this isn\'t contentious',
		'parent_fullname': f'comment_{root.id}',
		'parent_level': root.level,
	})

	rereply1 = comments.comment_for_client(alice_client, post.id, {
		'body': 'no u',
		'parent_fullname': f'comment_{reply1.id}',
		'parent_level': reply1.level,
	})

	reply2 = comments.comment_for_client(alice_client, post.id, {
		'body': 'Good poast',
		'parent_fullname': f'comment_{root.id}',
		'parent_level': root.level,
	})

	assert 3 == db.query(Comment).filter_by(id=root.id).first().descendant_count
	assert 1 == db.query(Comment).filter_by(id=reply1.id).first().descendant_count
	assert 0 == db.query(Comment).filter_by(id=reply2.id).first().descendant_count
	assert 0 == db.query(Comment).filter_by(id=rereply1.id).first().descendant_count

@util.no_rate_limit
def test_more_button_label_in_deep_threads(accounts, submissions, comments):
	db = db_session()
	alice_client, alice = accounts.client_and_user_for_account('Alice')

	post = submissions.submission_for_client(alice_client, {
		'title': 'Counting thread',
		'body': 'Count to 25',
	})
	post_id = post.id

	c = comments.comment_for_client(alice_client, post.id, {
		'body': '1',
	})
	for i in range(1, 25 + 1):
		c = comments.comment_for_client(alice_client, post.id, {
			'body': str(i),
			'parent_fullname': f'comment_{c.id}',
			'parent_level': c.level,
		})
		if i % 5 == 0:
			# only look every 5 posts to make this test not _too_ unbearably slow
			view_post_response = alice_client.get(f'/post/{post.id}')
			assert 200 == view_post_response.status_code
			if i <= RENDER_DEPTH_LIMIT - 1:
				assert f'More comments ({i - RENDER_DEPTH_LIMIT + 1})' not in view_post_response.text
			else:
				assert f'More comments ({i - RENDER_DEPTH_LIMIT + 1})' in view_post_response.text

@util.no_rate_limit
def test_bulk_update_descendant_count_quick(accounts, submissions, comments):
	"""
	1. Add two thin/non-robust posts with 20 nested comments each. Do not properly set descendant_count
	2. Do the descendant_count bulk update thing
	3. Ensure that the descendant_counts are correct
	4. Delete the comments/posts
	"""
	with app.app_context():
		db = db_session()

		lastname = ''.join(random.choice('aio') + random.choice('bfkmprst') for i in range(3))
		alice = User(**{
			"username": f"alice_{lastname}",
			"original_username": f"alice_{lastname}",
			"admin_level": 0,
			"password":"themotteuser",
			"email":None,
			"ban_evade":0,
			"profileurl":"/e/feather.webp"
		})
		db.add(alice)
		db.commit()
		posts = []
		for i in range(2):
			post = Submission(**{
				'private': False,
				'author_id': alice.id,
				'over_18': False,
				'app_id': None,
				'is_bot': False,
				'url': None,
				'body': f'This is a post by {alice.username}',
				'body_html': f'This is a post by {alice.username}',
				'embed_url': None,
				'title': f'Clever unique post title number {i}',
				'title_html': f'Clever unique post title number {i}',
				'ghost': False,
				'state_mod': StateMod.VISIBLE,
			})
			db.add(post)
			db.commit()
			posts.append(post)
			parent_comment = None
			top_comment = None
			for j in range(20):
				comment = Comment(**{
					'author_id': alice.id,
					'parent_submission': str(post.id),
					'parent_comment_id': parent_comment.id if parent_comment else None,
					'top_comment_id': top_comment.id if top_comment else None,
					'level': parent_comment.level + 1 if parent_comment else 1,
					'over_18': False,
					'is_bot': False,
					'app_id': None,
					'body_html': f'reply {i} {j}',
					'body': f'reply {i} {j}',
					'ghost': False,
					'state_mod': StateMod.VISIBLE,
				})
				if parent_comment is None:
					top_comment = comment
				parent_comment = comment
				db.add(comment)
				db.commit()
		sorted_comments_0 = sorted(posts[0].comments, key=lambda c: c.id)
		sorted_comments_1 = sorted(posts[1].comments, key=lambda c: c.id)
		assert [i+1 for i in range(20)] == [c.level for c in sorted_comments_0]
		assert [i+1 for i in range(20)] == [c.level for c in sorted_comments_1]
		assert [0 for i in range(20)] == [c.descendant_count for c in sorted_comments_0]
		assert [0 for i in range(20)] == [c.descendant_count for c in sorted_comments_1]
		bulk_recompute_descendant_counts(
			lambda q: q.where(Comment.parent_submission == posts[0].id),
			db
		)
		sorted_comments_0 = sorted(posts[0].comments, key=lambda c: c.id)
		sorted_comments_1 = sorted(posts[1].comments, key=lambda c: c.id)
		assert [i+1 for i in range(20)] == [c.level for c in sorted_comments_0]
		assert [i+1 for i in range(20)] == [c.level for c in sorted_comments_1]
		assert [20-i-1 for i in range(20)] == [c.descendant_count for c in sorted_comments_0]
		assert [0 for i in range(20)] == [c.descendant_count for c in sorted_comments_1]
		for post in posts:
			for comment in post.comments:
				db.delete(comment)
			db.delete(post)
		db.commit()
