"""
Provide demonstration site functionality.
"""

from __future__ import annotations

from contextlib import AsyncExitStack, asynccontextmanager
from typing import TYPE_CHECKING, final

from typing_extensions import override

from betty import load, generate, serve
from betty.extension.cotton_candy import CottonCandy
from betty.extension.cotton_candy.config import CottonCandyConfiguration
from betty.extension.http_api_doc import HttpApiDoc
from betty.extension.maps import Maps
from betty.extension.trees import Trees
from betty.extension.wikipedia import Wikipedia
from betty.load import LoadAncestryEvent
from betty.locale.date import Date, DateRange
from betty.locale.localizable import plain, Localizable
from betty.locale.localizer import DEFAULT_LOCALIZER
from betty.model.ancestry import (
    Place,
    PlaceName,
    Person,
    Presence,
    PersonName,
    Link,
    Source,
    Citation,
    Event,
    Enclosure,
    Note,
)
from betty.model.event_type import Marriage, Birth, Death
from betty.model.presence_role import Subject
from betty.project import (
    LocaleConfiguration,
    ExtensionConfiguration,
    EntityReference,
    Project,
)
from betty.project.extension import Extension
from betty.serve import Server, NoPublicUrlBecauseServerNotStartedError

if TYPE_CHECKING:
    from betty.event_dispatcher import EventHandlerRegistry
    from betty.plugin import PluginId
    from betty.app import App
    from collections.abc import AsyncIterator
    from betty.model import Entity


async def _load_ancestry(event: LoadAncestryEvent) -> None:
    def _load(*entities: Entity):
        event.project.ancestry.add(*entities)

    netherlands = Place(
        id="betty-demo-netherlands",
        names=[
            PlaceName(name="Netherlands"),
            PlaceName(
                name="Nederland",
                locale="nl",
            ),
            PlaceName(
                name="Нідерланди",
                locale="uk",
            ),
            PlaceName(
                name="Pays-Bas",
                locale="fr",
            ),
        ],
        links=[Link("https://en.wikipedia.org/wiki/Netherlands")],
    )
    _load(netherlands)

    north_holland = Place(
        id="betty-demo-north-holland",
        names=[
            PlaceName(name="North Holland"),
            PlaceName(
                name="Noord-Holland",
                locale="nl",
            ),
            PlaceName(
                name="Північна Голландія",
                locale="uk",
            ),
            PlaceName(
                name="Hollande-Septentrionale",
                locale="fr",
            ),
        ],
        links=[
            Link("https://en.wikipedia.org/wiki/North_Holland"),
            Link("https://www.noord-holland.nl/Home"),
        ],
    )
    _load(Enclosure(encloses=north_holland, enclosed_by=netherlands))
    _load(north_holland)

    amsterdam_note = Note(
        """
Did you know that while Amsterdam is the country's official capital, The Hague is the Netherlands' administrative center and seat of government?
    """
    )

    amsterdam = Place(
        id="betty-demo-amsterdam",
        names=[
            PlaceName(name="Amsterdam"),
            PlaceName(
                name="Амстерда́м",
                locale="uk",
            ),
        ],
        links=[
            Link("https://nl.wikipedia.org/wiki/Amsterdam"),
            Link("https://www.amsterdam.nl/"),
        ],
        notes=[amsterdam_note],
    )
    _load(Enclosure(encloses=amsterdam, enclosed_by=north_holland))
    _load(amsterdam)

    ilpendam = Place(
        id="betty-demo-ilpendam",
        names=[
            PlaceName(name="Ilpendam"),
            PlaceName(
                name="Илпендам",
                locale="uk",
            ),
        ],
        links=[Link("https://nl.wikipedia.org/wiki/Ilpendam")],
    )
    _load(Enclosure(encloses=ilpendam, enclosed_by=north_holland))
    _load(ilpendam)

    personal_accounts = Source(
        id="betty-demo-personal-accounts",
        name="Personal accounts",
    )
    _load(personal_accounts)

    cite_first_person_account = Citation(
        id="betty-demo-first-person-account",
        source=personal_accounts,
    )
    _load(cite_first_person_account)

    noord_hollands_archief = Source(
        id="betty-demo-noord-hollands-archief",
        name="Noord-Hollands Archief",
        links=[Link("https://noord-hollandsarchief.nl/")],
    )
    _load(noord_hollands_archief)

    bevolkingsregister_amsterdam = Source(
        id="betty-demo-bevolkingsregister-amsterdam",
        name="Bevolkingsregister Amsterdam",
        author="Gemeente Amsterdam",
        publisher="Gemeente Amsterdam",
        contained_by=noord_hollands_archief,
    )
    _load(bevolkingsregister_amsterdam)

    david_marinus_lankester = Person(id="betty-demo-david-marinus-lankester")
    _load(
        PersonName(
            person=david_marinus_lankester,
            individual="David Marinus",
            affiliation="Lankester",
        ),
        david_marinus_lankester,
    )

    geertruida_van_ling = Person(id="betty-demo-geertruida-van-ling")
    _load(
        PersonName(
            person=geertruida_van_ling,
            individual="Geertruida",
            affiliation="Van Ling",
        ),
        geertruida_van_ling,
    )

    marriage_of_dirk_jacobus_lankester_and_jannigje_palsen = Event(
        id="betty-demo-marriage-of-dirk-jacobus-lankester-and-jannigje-palsen",
        event_type=Marriage,
        date=Date(1922, 7, 4),
        place=ilpendam,
    )
    _load(marriage_of_dirk_jacobus_lankester_and_jannigje_palsen)

    birth_of_dirk_jacobus_lankester = Event(
        id="betty-demo-birth-of-dirk-jacobus-lankester",
        event_type=Birth,
        date=Date(1897, 8, 25),
        place=amsterdam,
    )
    _load(birth_of_dirk_jacobus_lankester)

    death_of_dirk_jacobus_lankester = Event(
        id="betty-demo-death-of-dirk-jacobus-lankester",
        event_type=Death,
        date=Date(1986, 8, 18),
        place=amsterdam,
    )
    _load(death_of_dirk_jacobus_lankester)

    dirk_jacobus_lankester = Person(
        id="betty-demo-dirk-jacobus-lankester",
        parents=(david_marinus_lankester, geertruida_van_ling),
    )
    _load(
        PersonName(
            person=dirk_jacobus_lankester,
            individual="Dirk Jacobus",
            affiliation="Lankester",
        ),
        Presence(dirk_jacobus_lankester, Subject(), birth_of_dirk_jacobus_lankester),
        Presence(dirk_jacobus_lankester, Subject(), death_of_dirk_jacobus_lankester),
        Presence(
            dirk_jacobus_lankester,
            Subject(),
            marriage_of_dirk_jacobus_lankester_and_jannigje_palsen,
        ),
    )
    _load(dirk_jacobus_lankester)

    birth_of_marinus_david_lankester = Event(
        id="betty-demo-birth-of-marinus-david",
        event_type=Birth,
        date=DateRange(
            Date(1874, 1, 15),
            Date(1874, 3, 21),
            start_is_boundary=True,
            end_is_boundary=True,
        ),
        place=amsterdam,
    )
    _load(birth_of_marinus_david_lankester)

    death_of_marinus_david_lankester = Event(
        id="betty-demo-death-of-marinus-david",
        event_type=Death,
        date=Date(1971),
        place=amsterdam,
    )
    _load(death_of_marinus_david_lankester)

    marinus_david_lankester = Person(
        id="betty-demo-marinus-david-lankester",
        parents=(david_marinus_lankester, geertruida_van_ling),
    )
    _load(
        PersonName(
            person=marinus_david_lankester,
            individual="Marinus David",
            affiliation="Lankester",
        ),
        Presence(marinus_david_lankester, Subject(), birth_of_marinus_david_lankester),
        Presence(marinus_david_lankester, Subject(), death_of_marinus_david_lankester),
    )
    _load(marinus_david_lankester)

    birth_of_jacoba_gesina_lankester = Event(
        id="betty-demo-birth-of-jacoba-gesina",
        event_type=Birth,
        date=Date(1900, 3, 14),
        place=amsterdam,
    )
    _load(birth_of_jacoba_gesina_lankester)

    jacoba_gesina_lankester = Person(
        id="betty-demo-jacoba-gesina-lankester",
        parents=(david_marinus_lankester, geertruida_van_ling),
    )
    _load(
        PersonName(
            person=jacoba_gesina_lankester,
            individual="Jacoba Gesina",
            affiliation="Lankester",
        ),
        Presence(jacoba_gesina_lankester, Subject(), birth_of_jacoba_gesina_lankester),
    )
    _load(jacoba_gesina_lankester)

    jannigje_palsen = Person(id="betty-demo-jannigje-palsen")
    _load(
        PersonName(
            person=jannigje_palsen,
            individual="Jannigje",
            affiliation="Palsen",
        ),
        Presence(
            jannigje_palsen,
            Subject(),
            marriage_of_dirk_jacobus_lankester_and_jannigje_palsen,
        ),
        jannigje_palsen,
    )

    marriage_of_johan_de_boer_and_liberta_lankester = Event(
        id="betty-demo-marriage-of-johan-de-boer-and-liberta-lankester",
        event_type=Marriage,
        date=Date(1953, 6, 19),
        place=amsterdam,
    )
    _load(marriage_of_johan_de_boer_and_liberta_lankester)

    cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam = Citation(
        id="betty-demo-birth-of-liberta-lankester-from-bevolkingsregister-amsterdam",
        source=bevolkingsregister_amsterdam,
        location=plain("Amsterdam"),
    )
    _load(cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam)

    birth_of_liberta_lankester = Event(
        id="betty-demo-birth-of-liberta-lankester",
        event_type=Birth,
        date=Date(1929, 12, 22),
        place=amsterdam,
        citations=[cite_birth_of_liberta_lankester_from_bevolkingsregister_amsterdam],
    )
    _load(birth_of_liberta_lankester)

    death_of_liberta_lankester = Event(
        id="betty-demo-death-of-liberta-lankester",
        event_type=Death,
        date=Date(2015, 1, 17),
        place=amsterdam,
        citations=[cite_first_person_account],
    )
    _load(death_of_liberta_lankester)

    liberta_lankester_note = Note(
        """
Did you know that Liberta "Betty" Lankester is Betty's namesake?
    """
    )

    liberta_lankester = Person(
        id="betty-demo-liberta-lankester",
        parents=(dirk_jacobus_lankester, jannigje_palsen),
        notes=[liberta_lankester_note],
    )
    _load(
        PersonName(
            person=liberta_lankester,
            individual="Liberta",
            affiliation="Lankester",
        ),
        PersonName(
            person=liberta_lankester,
            individual="Betty",
        ),
        Presence(liberta_lankester, Subject(), birth_of_liberta_lankester),
        Presence(liberta_lankester, Subject(), death_of_liberta_lankester),
        Presence(
            liberta_lankester,
            Subject(),
            marriage_of_johan_de_boer_and_liberta_lankester,
        ),
    )
    _load(liberta_lankester)

    birth_of_johan_de_boer = Event(
        id="betty-demo-birth-of-johan-de-boer",
        event_type=Birth,
        date=Date(1930, 6, 20),
        place=amsterdam,
    )
    _load(birth_of_johan_de_boer)

    death_of_johan_de_boer = Event(
        id="betty-demo-death-of-johan-de-boer",
        event_type=Death,
        date=Date(1999, 3, 10),
        place=amsterdam,
        citations=[cite_first_person_account],
    )
    _load(death_of_johan_de_boer)

    johan_de_boer = Person(id="betty-demo-johan-de-boer")
    _load(
        PersonName(
            person=johan_de_boer,
            individual="Johan",
            affiliation="De Boer",
        ),
        PersonName(
            person=johan_de_boer,
            individual="Hans",
        ),
        Presence(johan_de_boer, Subject(), birth_of_johan_de_boer),
        Presence(johan_de_boer, Subject(), death_of_johan_de_boer),
        Presence(
            johan_de_boer,
            Subject(),
            marriage_of_johan_de_boer_and_liberta_lankester,
        ),
        johan_de_boer,
    )

    parent_of_bart_feenstra_child_of_liberta_lankester = Person(
        id="betty-demo-parent-of-bart-feenstra-child-of-liberta-lankester",
        parents=(johan_de_boer, liberta_lankester),
    )
    _load(
        PersonName(
            person=parent_of_bart_feenstra_child_of_liberta_lankester,
            individual="Bart's parent",
        )
    )
    _load(parent_of_bart_feenstra_child_of_liberta_lankester)

    bart_feenstra = Person(
        id="betty-demo-bart-feenstra",
        parents=(parent_of_bart_feenstra_child_of_liberta_lankester,),
    )
    _load(
        PersonName(
            person=bart_feenstra,
            individual="Bart",
            affiliation="Feenstra",
        )
    )
    _load(bart_feenstra)


@final
class Demo(Extension):
    """
    Provide demonstration site functionality.
    """

    @override
    @classmethod
    def plugin_id(cls) -> PluginId:
        return "demo"

    @override
    @classmethod
    def plugin_label(cls) -> Localizable:
        return plain("Demo")

    @override
    @classmethod
    def depends_on(cls) -> set[PluginId]:
        return {
            CottonCandy.plugin_id(),
            HttpApiDoc.plugin_id(),
            Maps.plugin_id(),
            Trees.plugin_id(),
            Wikipedia.plugin_id(),
        }

    @override
    def register_event_handlers(self, registry: EventHandlerRegistry) -> None:
        registry.add_handler(LoadAncestryEvent, _load_ancestry)


@final
class DemoServer(Server):
    """
    Serve the Betty demonstration site.
    """

    def __init__(
        self,
        app: App,
    ):
        super().__init__(localizer=DEFAULT_LOCALIZER)
        self._app = app
        self._server: Server | None = None
        self._exit_stack = AsyncExitStack()

    @override
    @property
    def public_url(self) -> str:
        if self._server is not None:
            return self._server.public_url
        raise NoPublicUrlBecauseServerNotStartedError()

    @override
    async def start(self) -> None:
        await super().start()
        try:
            project = await self._exit_stack.enter_async_context(
                demo_project(self._app)
            )
            self._localizer = self._app.localizer
            await load.load(project)
            self._server = serve.BuiltinProjectServer(project)
            await self._exit_stack.enter_async_context(self._server)
            project.configuration.base_url = self._server.public_url
            await generate.generate(project)
        except BaseException:
            await self.stop()
            raise
        await self.assert_available()

    @override
    async def stop(self) -> None:
        await self._exit_stack.aclose()
        await super().stop()


@asynccontextmanager
async def demo_project(app: App) -> AsyncIterator[Project]:
    """
    Create a new demonstration project.
    """
    async with Project.new_temporary(app) as project:
        project.configuration.name = Demo.plugin_id()
        project.configuration.extensions.append(ExtensionConfiguration(Demo))
        project.configuration.extensions.append(
            ExtensionConfiguration(
                CottonCandy,
                extension_configuration=CottonCandyConfiguration(
                    featured_entities=[
                        EntityReference(Place, "betty-demo-amsterdam"),
                        EntityReference(Person, "betty-demo-liberta-lankester"),
                        EntityReference(Place, "betty-demo-netherlands"),
                    ],
                ),
            )
        )
        # Include all of the translations Betty ships with.
        project.configuration.locales.replace(
            LocaleConfiguration(
                "en-US",
                alias="en",
            ),
            LocaleConfiguration(
                "nl-NL",
                alias="nl",
            ),
            LocaleConfiguration(
                "fr-FR",
                alias="fr",
            ),
            LocaleConfiguration(
                "uk",
                alias="uk",
            ),
            LocaleConfiguration(
                "de-DE",
                alias="de",
            ),
        )
        async with project:
            yield project
