from __future__ import annotations

from typing import List, Dict, Any, Tuple, Optional
from dataclasses import dataclass
import logging

import requests

from piket_server import Person, db


@dataclass(frozen=True)
class SparseAardbeiPerson:
    full_name: str
    display_name: str
    aardbei_id: int
    is_leader: bool

    @classmethod
    def from_aardbei_dict(cls, data: Dict[str, Any]) -> SparseAardbeiPerson:
        return cls(
            full_name=data["member"]["person"]["full_name"],
            display_name=data["member"]["display_name"],
            aardbei_id=data["member"]["person"]["id"],
            is_leader=data["member"]["is_leader"],
        )


@dataclass(frozen=True)
class AardbeiMatch:
    local: Person
    remote: SparseAardbeiPerson


@dataclass(frozen=True)
class AardbeiLink:
    matches: List[AardbeiMatch]
    """People that exist on both sides, but aren't linked in the people table."""
    altered_name: List[AardbeiMatch]
    """People that are already linked but changed one of their names."""
    remote_only: List[SparseAardbeiPerson]
    """People that only exist on the remote."""


def get_aardbei_people(token: str) -> List[SparseAardbeiPerson]:
    resp = requests.get(
        "https://aardbei.app/api/groups/0/", headers={"Authorization": f"Group {token}"}
    )
    resp.raise_for_status()

    members = resp.json()["group"]["members"]

    return [SparseAardbeiPerson.from_aardbei_dict(x) for x in members]


def match_local_aardbei(aardbei_people: List[SparseAardbeiPerson]) -> AardbeiLink:
    matches: List[AardbeiMatch] = []
    altered_name: List[AardbeiMatch] = []
    remote_only: List[SparseAardbeiPerson] = []

    for aardbei_person in aardbei_people:
        p: Optional[Person] = Person.query.filter_by(
            aardbei_id=aardbei_person.aardbei_id
        ).one_or_none()

        if p is not None:
            if (
                p.full_name != aardbei_person.full_name
                or p.display_name != aardbei_person.display_name
            ):
                altered_name.append(AardbeiMatch(p, aardbei_person))

            else:
                logging.info(
                    "OK: %s / %s (L%s/R%s)",
                    p.full_name,
                    p.display_name,
                    p.person_id,
                    p.aardbei_id,
                )

            continue

        p = Person.query.filter_by(full_name=aardbei_person.full_name).one_or_none()

        if p is not None:
            matches.append(AardbeiMatch(p, aardbei_person))
        else:
            remote_only.append(aardbei_person)

    return AardbeiLink(matches, altered_name, remote_only)


def link_matches(matches: List[AardbeiMatch]) -> None:
    for match in matches:
        match.local.aardbei_id = match.remote.aardbei_id
        match.local.display_name = match.remote.display_name
        logging.info(
            "Linking local %s (%s) to remote %s (%s)",
            match.local.full_name,
            match.local.person_id,
            match.remote.display_name,
            match.remote.aardbei_id,
        )

        db.session.add(match.local)


def create_missing(missing: List[SparseAardbeiPerson]) -> None:
    for person in missing:
        pnew = Person(
            full_name=person.full_name,
            display_name=person.display_name,
            aardbei_id=person.aardbei_id,
            active=False,
        )
        logging.info(
            "Creating new person for %s (%s)", person.full_name, person.aardbei_id
        )
        db.session.add(pnew)


def update_names(matches: List[AardbeiMatch]) -> None:
    for match in matches:
        p = match.local
        aardbei_person = match.remote

        changed = False

        if p.full_name != aardbei_person.full_name:
            logging.info(
                "Updating %s (L%s/R%s) full name %s to %s",
                aardbei_person.full_name,
                p.person_id,
                aardbei_person.aardbei_id,
                p.full_name,
                aardbei_person.full_name,
            )
            p.full_name = aardbei_person.full_name
            changed = True

        if p.display_name != aardbei_person.display_name:
            logging.info(
                "Updating %s (L%s/R%s) display name %s to %s",
                aardbei_person.full_name,
                p.person_id,
                aardbei_person.aardbei_id,
                p.display_name,
                aardbei_person.display_name,
            )
            p.display_name = aardbei_person.display_name

            changed = True

        assert changed, "got match but didn't update anything"

        db.session.add(p)


if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO)

    token = input("Token: ")
    aardbei_people = get_aardbei_people(token)
    link = match_local_aardbei(aardbei_people)
    link_matches(link.matches)
    create_missing(link.remote_only)
    update_names(link.altered_name)

    confirm = input("Commit? Y/N")
    if confirm.lower() == "y":
        print("Committing.")
        db.session.commit()
    else:
        print("Not committing.")