# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

import pytest

from archery.release import (
    Release, MajorRelease, MinorRelease, PatchRelease,
    Jira, Version, Issue, CommitTitle, Commit
)
from archery.testing import DotDict


# subset of issues per revision
_issues = {
    "1.0.1": [
        Issue("ARROW-9684", type="Bug", summary="[C++] Title"),
        Issue("ARROW-9667", type="New Feature", summary="[Crossbow] Title"),
        Issue("ARROW-9659", type="Bug", summary="[C++] Title"),
        Issue("ARROW-9644", type="Bug", summary="[C++][Dataset] Title"),
        Issue("ARROW-9643", type="Bug", summary="[C++] Title"),
        Issue("ARROW-9609", type="Bug", summary="[C++] Title"),
        Issue("ARROW-9606", type="Bug", summary="[C++][Dataset] Title")
    ],
    "1.0.0": [
        Issue("ARROW-300", type="New Feature", summary="[Format] Title"),
        Issue("ARROW-4427", type="Task", summary="[Doc] Title"),
        Issue("ARROW-5035", type="Improvement", summary="[C#] Title"),
        Issue("ARROW-8473", type="Bug", summary="[Rust] Title"),
        Issue("ARROW-8472", type="Bug", summary="[Go][Integration] Title"),
        Issue("ARROW-8471", type="Bug", summary="[C++][Integration] Title"),
        Issue("ARROW-8974", type="Improvement", summary="[C++] Title"),
        Issue("ARROW-8973", type="New Feature", summary="[Java] Title")
    ],
    "0.17.1": [
        Issue("ARROW-8684", type="Bug", summary="[Python] Title"),
        Issue("ARROW-8657", type="Bug", summary="[C++][Parquet] Title"),
        Issue("ARROW-8641", type="Bug", summary="[Python] Title"),
        Issue("ARROW-8609", type="Bug", summary="[C++] Title"),
    ],
    "0.17.0": [
        Issue("ARROW-2882", type="New Feature", summary="[C++][Python] Title"),
        Issue("ARROW-2587", type="Bug", summary="[Python] Title"),
        Issue("ARROW-2447", type="Improvement", summary="[C++] Title"),
        Issue("ARROW-2255", type="Bug", summary="[Integration] Title"),
        Issue("ARROW-1907", type="Bug", summary="[C++/Python] Title"),
        Issue("ARROW-1636", type="New Feature", summary="[Format] Title")
    ]
}


class FakeJira(Jira):

    def __init__(self):
        pass

    def project_versions(self, project='ARROW'):
        return [
            Version.parse("3.0.0", released=False),
            Version.parse("2.0.0", released=False),
            Version.parse("1.1.0", released=False),
            Version.parse("1.0.1", released=False),
            Version.parse("1.0.0", released=True),
            Version.parse("0.17.1", released=True),
            Version.parse("0.17.0", released=True),
            Version.parse("0.16.0", released=True),
            Version.parse("0.15.2", released=True),
            Version.parse("0.15.1", released=True),
            Version.parse("0.15.0", released=True),
        ]

    def project_issues(self, version, project='ARROW'):
        return _issues[str(version)]


@pytest.fixture
def fake_jira():
    return FakeJira()


def test_version(fake_jira):
    v = Version.parse("1.2.5")
    assert str(v) == "1.2.5"
    assert v.major == 1
    assert v.minor == 2
    assert v.patch == 5
    assert v.released is False
    assert v.release_date is None

    v = Version.parse("1.0.0", released=True, release_date="2020-01-01")
    assert str(v) == "1.0.0"
    assert v.major == 1
    assert v.minor == 0
    assert v.patch == 0
    assert v.released is True
    assert v.release_date == "2020-01-01"


def test_issue(fake_jira):
    i = Issue("ARROW-1234", type='Bug', summary="title")
    assert i.key == "ARROW-1234"
    assert i.type == "Bug"
    assert i.summary == "title"
    assert i.project == "ARROW"
    assert i.number == 1234

    i = Issue("PARQUET-1111", type='Improvement', summary="another title")
    assert i.key == "PARQUET-1111"
    assert i.type == "Improvement"
    assert i.summary == "another title"
    assert i.project == "PARQUET"
    assert i.number == 1111

    fake_jira_issue = DotDict({
        'key': 'ARROW-2222',
        'fields': {
            'issuetype': {
                'name': 'Feature'
            },
            'summary': 'Issue title'
        }
    })
    i = Issue.from_jira(fake_jira_issue)
    assert i.key == "ARROW-2222"
    assert i.type == "Feature"
    assert i.summary == "Issue title"
    assert i.project == "ARROW"
    assert i.number == 2222


def test_commit_title():
    t = CommitTitle.parse(
        "ARROW-9598: [C++][Parquet] Fix writing nullable structs"
    )
    assert t.project == "ARROW"
    assert t.issue == "ARROW-9598"
    assert t.components == ["C++", "Parquet"]
    assert t.summary == "Fix writing nullable structs"

    t = CommitTitle.parse(
        "ARROW-8002: [C++][Dataset][R] Support partitioned dataset writing"
    )
    assert t.project == "ARROW"
    assert t.issue == "ARROW-8002"
    assert t.components == ["C++", "Dataset", "R"]
    assert t.summary == "Support partitioned dataset writing"

    t = CommitTitle.parse(
        "ARROW-9600: [Rust][Arrow] pin older version of proc-macro2 during "
        "build"
    )
    assert t.project == "ARROW"
    assert t.issue == "ARROW-9600"
    assert t.components == ["Rust", "Arrow"]
    assert t.summary == "pin older version of proc-macro2 during build"

    t = CommitTitle.parse("[Release] Update versions for 1.0.0")
    assert t.project is None
    assert t.issue is None
    assert t.components == ["Release"]
    assert t.summary == "Update versions for 1.0.0"

    t = CommitTitle.parse("[Python][Doc] Fix rst role dataset.rst (#7725)")
    assert t.project is None
    assert t.issue is None
    assert t.components == ["Python", "Doc"]
    assert t.summary == "Fix rst role dataset.rst (#7725)"

    t = CommitTitle.parse(
        "PARQUET-1882: [C++] Buffered Reads should allow for 0 length"
    )
    assert t.project == 'PARQUET'
    assert t.issue == 'PARQUET-1882'
    assert t.components == ["C++"]
    assert t.summary == "Buffered Reads should allow for 0 length"

    t = CommitTitle.parse(
        "ARROW-9340 [R] Use CRAN version of decor package "
        "\nsomething else\n"
        "\nwhich should be truncated"
    )
    assert t.project == 'ARROW'
    assert t.issue == 'ARROW-9340'
    assert t.components == ["R"]
    assert t.summary == "Use CRAN version of decor package "


def test_release_basics(fake_jira):
    r = Release.from_jira("1.0.0", jira=fake_jira)
    assert isinstance(r, MajorRelease)
    assert r.is_released is True
    assert r.branch == 'master'
    assert r.tag == 'apache-arrow-1.0.0'

    r = Release.from_jira("1.1.0", jira=fake_jira)
    assert isinstance(r, MinorRelease)
    assert r.is_released is False
    assert r.branch == 'maint-1.x.x'
    assert r.tag == 'apache-arrow-1.1.0'

    # minor releases before 1.0 are treated as major releases
    r = Release.from_jira("0.17.0", jira=fake_jira)
    assert isinstance(r, MajorRelease)
    assert r.is_released is True
    assert r.branch == 'master'
    assert r.tag == 'apache-arrow-0.17.0'

    r = Release.from_jira("0.17.1", jira=fake_jira)
    assert isinstance(r, PatchRelease)
    assert r.is_released is True
    assert r.branch == 'maint-0.17.x'
    assert r.tag == 'apache-arrow-0.17.1'


def test_previous_and_next_release(fake_jira):
    r = Release.from_jira("3.0.0", jira=fake_jira)
    assert isinstance(r.previous, MajorRelease)
    assert r.previous.version == Version.parse("2.0.0")
    with pytest.raises(ValueError, match="There is no upcoming release set"):
        assert r.next

    r = Release.from_jira("2.0.0", jira=fake_jira)
    assert isinstance(r.previous, MajorRelease)
    assert isinstance(r.next, MajorRelease)
    assert r.previous.version == Version.parse("1.0.0")
    assert r.next.version == Version.parse("3.0.0")

    r = Release.from_jira("1.1.0", jira=fake_jira)
    assert isinstance(r.previous, MajorRelease)
    assert isinstance(r.next, MajorRelease)
    assert r.previous.version == Version.parse("1.0.0")
    assert r.next.version == Version.parse("2.0.0")

    r = Release.from_jira("1.0.0", jira=fake_jira)
    assert isinstance(r.next, MajorRelease)
    assert isinstance(r.previous, MajorRelease)
    assert r.previous.version == Version.parse("0.17.0")
    assert r.next.version == Version.parse("2.0.0")

    r = Release.from_jira("0.17.0", jira=fake_jira)
    assert isinstance(r.previous, MajorRelease)
    assert r.previous.version == Version.parse("0.16.0")

    r = Release.from_jira("0.15.2", jira=fake_jira)
    assert isinstance(r.previous, PatchRelease)
    assert isinstance(r.next, MajorRelease)
    assert r.previous.version == Version.parse("0.15.1")
    assert r.next.version == Version.parse("0.16.0")

    r = Release.from_jira("0.15.1", jira=fake_jira)
    assert isinstance(r.previous, MajorRelease)
    assert isinstance(r.next, PatchRelease)
    assert r.previous.version == Version.parse("0.15.0")
    assert r.next.version == Version.parse("0.15.2")


def test_release_issues(fake_jira):
    # major release issues
    r = Release.from_jira("1.0.0", jira=fake_jira)
    assert r.issues.keys() == set([
        "ARROW-300",
        "ARROW-4427",
        "ARROW-5035",
        "ARROW-8473",
        "ARROW-8472",
        "ARROW-8471",
        "ARROW-8974",
        "ARROW-8973"
    ])
    # minor release issues
    r = Release.from_jira("0.17.0", jira=fake_jira)
    assert r.issues.keys() == set([
        "ARROW-2882",
        "ARROW-2587",
        "ARROW-2447",
        "ARROW-2255",
        "ARROW-1907",
        "ARROW-1636",
    ])
    # patch release issues
    r = Release.from_jira("1.0.1", jira=fake_jira)
    assert r.issues.keys() == set([
        "ARROW-9684",
        "ARROW-9667",
        "ARROW-9659",
        "ARROW-9644",
        "ARROW-9643",
        "ARROW-9609",
        "ARROW-9606"
    ])


@pytest.mark.parametrize(('version', 'ncommits'), [
    ("1.0.0", 771),
    ("0.17.1", 27),
    ("0.17.0", 569),
    ("0.15.1", 41)
])
def test_release_commits(fake_jira, version, ncommits):
    r = Release.from_jira(version, jira=fake_jira)
    assert len(r.commits) == ncommits
    for c in r.commits:
        assert isinstance(c, Commit)
        assert isinstance(c.title, CommitTitle)
        assert c.url.endswith(c.hexsha)


def test_maintenance_patch_selection(fake_jira):
    r = Release.from_jira("0.17.1", jira=fake_jira)

    shas_to_pick = [
        c.hexsha for c in r.commits_to_pick(exclude_already_applied=False)
    ]
    expected = [
        '8939b4bd446ee406d5225c79d563a27d30fd7d6d',
        'bcef6c95a324417e85e0140f9745d342cd8784b3',
        '6002ec388840de5622e39af85abdc57a2cccc9b2',
        '9123dadfd123bca7af4eaa9455f5b0d1ca8b929d',
    ]
    assert shas_to_pick == expected
