import copy
from .base import (
    BaseModel,
    SourceMixin,
    LinkMixin,
    ContactDetailMixin,
    OtherNameMixin,
    IdentifierMixin,
)
from .schemas.post import schema as post_schema
from .schemas.person import schema as person_schema
from .schemas.membership import schema as membership_schema
from .schemas.organization import schema as org_schema
from ..utils import _make_pseudo_id
from ..exceptions import ScrapeValueError

# a copy of the org schema without sources
org_schema_no_sources = copy.deepcopy(org_schema)
org_schema_no_sources["properties"].pop("sources")


class Post(BaseModel):
    """
    A popolo-style Post
    """

    _type = "post"
    _schema = post_schema

    def __init__(
        self,
        *,
        label,
        role,
        organization_id=None,
        chamber=None,
        division_id=None,
        start_date="",
        end_date="",
        maximum_memberships=1
    ):
        super(Post, self).__init__()
        self.label = label
        self.role = role
        self.organization_id = pseudo_organization(organization_id, chamber)
        self.division_id = division_id
        self.start_date = start_date
        self.end_date = end_date
        self.maximum_memberships = maximum_memberships

    def __str__(self):
        return self.label


class Membership(BaseModel):
    """
    A popolo-style Membership.
    """

    _type = "membership"
    _schema = membership_schema

    def __init__(
        self,
        *,
        person_id,
        organization_id,
        post_id=None,
        role="",
        label="",
        start_date="",
        end_date="",
        person_name=""
    ):
        """
        Constructor for the Membership object.

        We require a person ID and organization ID, as required by the
        popolo spec. Additional arguments may be given, which match those
        defined by popolo.
        """
        super(Membership, self).__init__()
        self.person_id = person_id
        self.person_name = person_name
        self.organization_id = organization_id
        self.post_id = post_id
        self.start_date = start_date
        self.end_date = end_date
        self.role = role
        self.label = label

    def __str__(self):
        return self.person_id + " membership in " + self.organization_id


class Person(
    BaseModel,
    SourceMixin,
    ContactDetailMixin,
    LinkMixin,
    IdentifierMixin,
    OtherNameMixin,
):
    """
    Details for a Person in Popolo format.
    """

    _type = "person"
    _schema = person_schema

    def __init__(
        self,
        name,
        *,
        birth_date="",
        death_date="",
        biography="",
        image="",
        gender="",
        # specialty fields
        district=None,
        party=None,
        primary_org="",
        role="",
        start_date="",
        end_date="",
        primary_org_name=None
    ):
        super(Person, self).__init__()
        self.name = name
        self.birth_date = birth_date
        self.death_date = death_date
        self.biography = biography
        self.image = image
        self.gender = gender
        if primary_org:
            self.add_term(
                role,
                primary_org,
                district=district,
                start_date=start_date,
                end_date=end_date,
                org_name=primary_org_name,
            )
        if party:
            self.add_party(party)

    def add_membership(self, name_or_org, role="member", **kwargs):
        """
        add a membership in an organization and return the membership
        object in case there are more details to add
        """
        if isinstance(name_or_org, Organization):
            membership = Membership(
                person_id=self._id,
                person_name=self.name,
                organization_id=name_or_org._id,
                role=role,
                **kwargs
            )
        else:
            membership = Membership(
                person_id=self._id,
                person_name=self.name,
                organization_id=_make_pseudo_id(name=name_or_org),
                role=role,
                **kwargs
            )
        self._related.append(membership)
        return membership

    def add_party(self, party, **kwargs):
        membership = Membership(
            person_id=self._id,
            person_name=self.name,
            organization_id=_make_pseudo_id(classification="party", name=party),
            role="member",
            **kwargs
        )
        self._related.append(membership)

    def add_term(
        self,
        role,
        org_classification,
        *,
        district=None,
        start_date="",
        end_date="",
        label="",
        org_name=None,
        appointment=False
    ):
        if org_name:
            org_id = _make_pseudo_id(classification=org_classification, name=org_name)
        else:
            org_id = _make_pseudo_id(classification=org_classification)

        if district:
            if role:
                post_id = _make_pseudo_id(
                    label=district,
                    role=role,
                    organization__classification=org_classification,
                )
            else:
                post_id = _make_pseudo_id(
                    label=district, organization__classification=org_classification
                )
        elif appointment:
            post_id = _make_pseudo_id(
                role=role, organization__classification=org_classification
            )
        else:
            post_id = None
        membership = Membership(
            person_id=self._id,
            person_name=self.name,
            organization_id=org_id,
            post_id=post_id,
            role=role,
            start_date=start_date,
            end_date=end_date,
            label=label,
        )
        self._related.append(membership)
        return membership

    def __str__(self):
        return self.name


class Organization(BaseModel, SourceMixin, LinkMixin):
    """
    A single popolo-style Organization
    """

    _type = "organization"
    _schema = org_schema

    def __init__(self, name, *, classification="", parent_id=None, chamber=None):
        """
        Constructor for the Organization object.
        """
        super(Organization, self).__init__()
        self.name = name
        self.classification = classification
        self.parent_id = pseudo_organization(parent_id, chamber)

    def __str__(self):
        return self.name

    def validate(self):
        schema = None
        # these are implicitly declared & do not require sources
        if self.classification in (
            "party",
            "legislature",
            "upper",
            "lower",
            "executive",
        ):
            schema = org_schema_no_sources
        return super(Organization, self).validate(schema=schema)

    def add_post(self, label, role, **kwargs):
        post = Post(label=label, role=role, organization_id=self._id, **kwargs)
        self._related.append(post)
        return post

    def add_member(self, name_or_person, role="member", **kwargs):
        if isinstance(name_or_person, Person):
            membership = Membership(
                person_id=name_or_person._id,
                person_name=name_or_person.name,
                organization_id=self._id,
                role=role,
                **kwargs
            )
        else:
            membership = Membership(
                person_id=_make_pseudo_id(name=name_or_person),
                person_name=name_or_person,
                organization_id=self._id,
                role=role,
                **kwargs
            )
        self._related.append(membership)
        return membership


def pseudo_organization(organization, classification, default=None):
    """ helper for setting an appropriate ID for organizations """
    if organization and classification:
        raise ScrapeValueError("cannot specify both classification and organization")
    elif classification:
        return _make_pseudo_id(classification=classification)
    elif organization:
        if isinstance(organization, Organization):
            return organization._id
        elif isinstance(organization, str):
            return organization
        else:
            return _make_pseudo_id(**organization)
    elif default is not None:
        return _make_pseudo_id(classification=default)
    else:
        return None
