"""This module tests edge cases specific to the batch objects themselves,
without wider relevance and thus other location.
"""
import pytest

from utils import Commit, to_pr, pr_page, seen


def test_close_single(env, repo):
    """If a batch has a single PR and that PR gets closed, the batch should be
    inactive *and* blocked.
    """
    with repo:
        repo.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        [c] = repo.make_commits('master', Commit('b', tree={"b": "b"}))
        pr = repo.make_pr(head=c, target='master')
    env.run_crons()

    pr_id = to_pr(env, pr)
    batch_id = pr_id.batch_id
    assert pr_id.state == 'opened'
    assert batch_id.blocked
    Batches = env['runbot_merge.batch']
    assert Batches.search_count([]) == 1

    with repo:
        pr.close()

    assert pr_id.state == 'closed'
    assert batch_id.all_prs == pr_id
    assert batch_id.prs == pr_id.browse(())
    assert batch_id.blocked == "all prs are closed"
    assert not batch_id.active

    assert Batches.search_count([]) == 0

def test_close_multiple(env, make_repo2):
    Batches = env['runbot_merge.batch']
    repo1 = make_repo2('wheee')
    repo2 = make_repo2('wheeee')

    with repo1:
        repo1.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        repo1.make_commits('master', Commit('b', tree={"b": "b"}), ref='heads/a_pr')
        pr1 = repo1.make_pr(head='a_pr', target='master')

    with repo2:
        repo2.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        repo2.make_commits('master', Commit('b', tree={"b": "b"}), ref='heads/a_pr')
        pr2 = repo2.make_pr(head='a_pr', target='master')

    pr1_id = to_pr(env, pr1)
    pr2_id = to_pr(env, pr2)
    batch_id = pr1_id.batch_id
    assert pr2_id.batch_id == batch_id

    assert pr1_id.state == 'opened'
    assert pr2_id.state == 'opened'
    assert batch_id.all_prs == pr1_id | pr2_id
    assert batch_id.prs == pr1_id | pr2_id
    assert batch_id.active
    assert Batches.search_count([]) == 1

    with repo1:
        pr1.close()

    assert pr1_id.state == 'closed'
    assert pr2_id.state == 'opened'
    assert batch_id.all_prs == pr1_id | pr2_id
    assert batch_id.prs == pr2_id
    assert batch_id.active
    assert Batches.search_count([]) == 1

    with repo2:
        pr2.close()

    assert pr1_id.state == 'closed'
    assert pr2_id.state == 'closed'
    assert batch_id.all_prs == pr1_id | pr2_id
    assert batch_id.prs == env['runbot_merge.pull_requests'].browse(())
    assert not batch_id.active
    assert Batches.search_count([]) == 0

def test_inconsistent_target(env, project, make_repo2, users, page, config):
    """If a batch's PRs have inconsistent targets,

    - only open PRs should count
    - it should be clearly notified on the dash
    - the dash should not get hopelessly lost
    - there should be a wizard to split the batch / move a PR to a separate batch
    """
    # region setup
    Batches = env['runbot_merge.batch']
    repo1 = make_repo2('whe')
    repo2 = make_repo2('whee')
    repo3 = make_repo2('wheee')
    project.write({'branch_ids': [(0, 0, {'name': 'other'})]})

    with repo1:
        [m] = repo1.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        repo1.make_ref('heads/other', m)
        repo1.make_commits('master', Commit('b', tree={"b": "b"}), ref='heads/a_pr')
        pr1 = repo1.make_pr(head='a_pr', target='master')

        repo1.make_commits('master', Commit('b', tree={"c": "c"}), ref='heads/something_else')
        pr_other = repo1.make_pr(head='something_else', target='master')

    with repo2:
        [m] = repo2.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        repo2.make_ref("heads/other", m)
        repo2.make_commits('master', Commit('b', tree={"b": "b"}), ref='heads/a_pr')
        pr2 = repo2.make_pr(head='a_pr', target='master')

    with repo3:
        [m] = repo3.make_commits(None, Commit("a", tree={"a": "a"}), ref='heads/master')
        repo3.make_ref("heads/other", m)
        repo3.make_commits('master', Commit('b', tree={"b": "b"}), ref='heads/a_pr')
        pr3 = repo3.make_pr(head='a_pr', target='master')

    assert repo1.owner == repo2.owner == repo3.owner
    owner = repo1.owner
    # endregion

    # region closeable consistency

    [b] = Batches.search([('all_prs.label', '=', f'{owner}:a_pr')])
    assert b.target.name == 'master'
    assert len(b.prs) == 3
    assert len(b.all_prs) == 3

    with repo3:
        pr3.base = 'other'
    assert b.target.name == False
    assert len(b.prs) == 3
    assert len(b.all_prs) == 3

    with repo3:
        pr3.close()
    assert b.target.name == 'master'
    assert len(b.prs) == 2
    assert len(b.all_prs) == 3
    # endregion

    # region split batch
    pr1_id = to_pr(env, pr1)
    pr2_id = to_pr(env, pr2)
    with repo2:
        pr2.base = 'other'

    pr2_dashboard = pr_page(page, pr2)
    # The dashboard should have an alert
    s = pr2_dashboard.cssselect('.alert.alert-danger')
    assert s, "the dashboard should have an alert"
    assert s[0].text_content().strip() == f"""\
Inconsistent targets:

{pr1_id.display_name} has target 'master'
{pr2_id.display_name} has target 'other'\
"""
    assert not pr2_dashboard.cssselect('table'), "the batches table should be suppressed"

    assert b.target.name == False
    assert to_pr(env, pr_other).label == f'{owner}:something_else'
    # try staging
    with repo1:
        pr1.post_comment("hansen r+", config['role_reviewer']['token'])
        repo1.post_status(pr1.head, "success")
    with repo2:
        pr2.post_comment("hansen r+", config['role_reviewer']['token'])
        repo2.post_status(pr2.head, "success")
    env.run_crons()
    assert not pr1_id.blocked
    assert not pr2_id.blocked
    assert b.blocked == "Multiple target branches: 'other, master'"
    assert env['runbot_merge.stagings'].search_count([]) == 0

    act = pr2_id.button_split()
    assert act['type'] == 'ir.actions.act_window'
    assert act['views'] == [[False, 'form']]
    assert act['target'] == 'new'
    w = env[act['res_model']].browse([act['res_id']])
    w.new_label = f"{owner}:something_else"
    with pytest.raises(Exception):
        w.button_apply()
    w.new_label = f"{owner}:blah-blah-blah"
    w.button_apply()

    assert pr2_id.label == f"{owner}:blah-blah-blah"
    assert pr2_id.batch_id != to_pr(env, pr1).batch_id
    assert b.target.name == 'master'
    assert len(b.prs) == 1, "the PR has been moved off of this batch entirely"
    assert len(b.all_prs) == 2
    # endregion

    assert not pr1_id.blocked
    assert not pr1_id.batch_id.blocked
    assert not pr2_id.blocked
    assert not pr2_id.batch_id.blocked
    env.run_crons()

    assert env['runbot_merge.stagings'].search_count([])

def test_reopen_pr_in_staged_batch(env, project, make_repo2, config):
    """Reopening a closed PR from a staged batch should cancel the staging
    """
    repo1 = make_repo2('a')
    repo2 = make_repo2('b')

    with repo1:
        [m1, _] = repo1.make_commits(
            None,
            Commit('a', tree={'a': 'a'}),
            Commit('b', tree={'b': 'b'}),
            ref='heads/p',
        )
        repo1.make_ref('heads/master', m1)
        pr1 = repo1.make_pr(target='master', head='p')
    with repo2:
        [m2, _] = repo2.make_commits(
            None,
            Commit('a', tree={'a': 'a'}),
            Commit('b', tree={'b': 'b'}),
            ref='heads/p',
        )
        repo2.make_ref('heads/master', m2)
        pr2 = repo2.make_pr(target='master', head='p')

    pr1_id = to_pr(env, pr1)
    pr2_id = to_pr(env, pr2)
    batch_id = pr1_id.batch_id
    assert batch_id
    assert batch_id == pr2_id.batch_id

    with repo1:
        repo1.post_status(pr1.head, 'success')
        pr1.post_comment("hansen r+", config['role_reviewer']['token'])
    with repo2:
        pr2.close()

    env.run_crons(None)

    assert pr2_id.state == 'closed'
    assert batch_id.staging_ids.filtered(lambda s: s.active)

    with repo2:
        pr2.open()
    assert pr2_id.state == 'opened'
    assert not batch_id.staging_ids.filtered(lambda s: s.active)
    assert batch_id.blocked

def test_reopen_pr_in_merged_batch(env, project, make_repo2, config, users):
    """If the batch is merged, the pr should just be re-closed with a message
    """
    repo1 = make_repo2('a')
    repo2 = make_repo2('b')

    with repo1:
        [m1, _] = repo1.make_commits(
            None,
            Commit('a', tree={'a': 'a'}),
            Commit('b', tree={'b': 'b'}),
            ref='heads/p',
        )
        repo1.make_ref('heads/master', m1)
        pr1 = repo1.make_pr(target='master', head='p')
    with repo2:
        [m2, _] = repo2.make_commits(
            None,
            Commit('a', tree={'a': 'a'}),
            Commit('b', tree={'b': 'b'}),
            ref='heads/p',
        )
        repo2.make_ref('heads/master', m2)
        pr2 = repo2.make_pr(target='master', head='p')

    pr1_id = to_pr(env, pr1)
    pr2_id = to_pr(env, pr2)
    batch_id = pr1_id.batch_id
    assert batch_id
    assert batch_id == pr2_id.batch_id

    with repo1:
        repo1.post_status(pr1.head, 'success')
        pr1.post_comment("hansen r+", config['role_reviewer']['token'])
    with repo2:
        pr2.close()

    env.run_crons(None)

    with repo1, repo2:
        repo1.post_status('staging.master', 'success')
        repo2.post_status('staging.master', 'success')
    env.run_crons(None)


    assert pr1_id.state == 'merged'
    assert pr2_id.state == 'closed'

    with repo2:
        pr2.open()
    env.run_crons(None)

    assert pr2_id.closed
    assert pr2_id.state == 'closed'
    assert pr2.comments == [
        seen(env, pr2, users),
        (users['user'], 'Reopening a PR in a merged batch is not allowed, create a new PR.'),
    ]
