#  holidays
#  --------
#  A fast, efficient Python library for generating country, province and state
#  specific sets of holidays on the fly. It aims to make determining whether a
#  specific date is a holiday as fast and flexible as possible.
#
#  Authors: Vacanza Team and individual contributors (see CONTRIBUTORS file)
#           dr-prodigy <dr.prodigy.github@gmail.com> (c) 2017-2023
#           ryanss <ryanssdev@icloud.com> (c) 2014-2017
#  Website: https://github.com/vacanza/holidays
#  License: MIT (see LICENSE file)

from datetime import date
from gettext import gettext as tr
from typing import Optional

from holidays.calendars.gregorian import MAR, APR, JUN, JUL, SEP
from holidays.constants import GOVERNMENT, OPTIONAL, PUBLIC
from holidays.groups import ChristianHolidays, InternationalHolidays, StaticHolidays
from holidays.observed_holiday_base import (
    ObservedHolidayBase,
    ALL_TO_NEAREST_MON,
    SAT_SUN_TO_NEXT_MON,
    SAT_SUN_TO_NEXT_MON_TUE,
    SUN_TO_NEXT_MON,
    SUN_TO_NEXT_TUE,
)


class Canada(ObservedHolidayBase, ChristianHolidays, InternationalHolidays, StaticHolidays):
    """Canada holidays.

    References:
        * <https://en.wikipedia.org/wiki/Public_holidays_in_Canada>
        * <https://web.archive.org/web/20130703014214/http://www.hrsdc.gc.ca/eng/labour/overviews/employment_standards/holidays.shtml>
        * <https://www.alberta.ca/alberta-general-holidays>
        * <https://www2.gov.bc.ca/gov/content/employment-business/employment-standards-advice/employment-standards/statutory-holidays>
        * <http://web2.gov.mb.ca/laws/statutes/ccsm/r120e.php>
        * <https://www2.gnb.ca/content/gnb/en/departments/elg/local_government/content/governance/content/days_of_rest_act.html>
        * <https://www.ontario.ca/document/your-guide-employment-standards-act-0/public-holidays>
        * <https://www.gov.nu.ca/en/print/pdf/node/52266>
        * <https://www.officeholidays.com/countries/canada/>
        * <https://www.timeanddate.com/holidays/canada/>
        * <https://web.archive.org/web/20250122122256/https://www.warmuseum.ca/firstworldwar/history/after-the-war/remembrance/remembrance-day/>
        * <https://web.archive.org/web/20250428153936/https://www.thecanadianencyclopedia.ca/en/article/thanksgiving-day>
        * <https://web.archive.org/web/20250428154427/https://recherche-collection-search.bac-lac.gc.ca/eng/home/record?idnumber=9326&app=diawlmking&ecopy=80003QJW>
        * <https://web.archive.org/web/20240915001506/https://www.britannica.com/topic/Victoria-Day>
    """

    country = "CA"
    default_language = "en_CA"
    # %s (observed).
    observed_label = tr("%s (observed)")
    start_year = 1867
    subdivisions = (
        "AB",  # Alberta.
        "BC",  # British Columbia (Colombie-Britannique).
        "MB",  # Manitoba.
        "NB",  # New Brunswick (Nouveau-Brunswick).
        "NL",  # Newfoundland and Labrador (Terre-Neuve-et-Labrador).
        "NS",  # Nova Scotia (Nouvelle-Écosse).
        "NT",  # Northwest Territories (Territoires du Nord-Ouest).
        "NU",  # Nunavut.
        "ON",  # Ontario.
        "PE",  # Prince Edward Island (Île-du-Prince-Édouard).
        "QC",  # Quebec (Québec).
        "SK",  # Saskatchewan.
        "YT",  # Yukon.
    )
    subdivisions_aliases = {
        "Alberta": "AB",
        "British Columbia": "BC",
        "Colombie-Britannique": "BC",
        "Manitoba": "MB",
        "New Brunswick": "NB",
        "Nouveau-Brunswick": "NB",
        "Newfoundland and Labrador": "NL",
        "Terre-Neuve-et-Labrador": "NL",
        "Nova Scotia": "NS",
        "Nouvelle-Écosse": "NS",
        "Northwest Territories": "NT",
        "Territoires du Nord-Ouest": "NT",
        "Nunavut": "NU",
        "Ontario": "ON",
        "Prince Edward Island": "PE",
        "Île-du-Prince-Édouard": "PE",
        "Quebec": "QC",
        "Québec": "QC",
        "Saskatchewan": "SK",
        "Yukon": "YT",
    }
    supported_categories = (GOVERNMENT, OPTIONAL, PUBLIC)
    supported_languages = ("ar", "en_CA", "en_US", "fr", "th")

    def __init__(self, *args, **kwargs):
        ChristianHolidays.__init__(self)
        InternationalHolidays.__init__(self)
        StaticHolidays.__init__(self, CanadaStaticHolidays)
        kwargs.setdefault("observed_rule", SAT_SUN_TO_NEXT_MON)
        super().__init__(*args, **kwargs)

    def _get_nearest_monday(self, *args) -> Optional[date]:
        return self._get_observed_date(date(self._year, *args), rule=ALL_TO_NEAREST_MON)

    def _add_statutory_holidays(self):
        """Nationwide statutory holidays."""

        # New Year's Day.
        self._add_observed(self._add_new_years_day(tr("New Year's Day")))

        # Good Friday.
        self._add_good_friday(tr("Good Friday"))

        if self._year >= 1879:
            self._canada_day = self._add_holiday_jul_1(
                # Canada Day.
                tr("Canada Day")
                if self._year >= 1983
                # Dominion Day.
                else tr("Dominion Day")
            )

        if self._year >= 1894:
            # Labor Day.
            self._add_holiday_1st_mon_of_sep(tr("Labour Day"))

        # Christmas Day.
        self._add_christmas_day(tr("Christmas Day"))

    def _populate_public_holidays(self):
        self._add_statutory_holidays()

        self._add_observed(self._christmas_day)

    def _populate_government_holidays(self):
        """Holidays for federally regulated workplaces."""

        self._add_statutory_holidays()

        self._add_victoria_day()

        if self._year >= 1879:
            self._add_observed(self._canada_day)

        if self._year >= 2021:
            self._add_observed(
                # National Day for Truth and Reconciliation.
                self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))
            )

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

        self._add_observed(self._christmas_day, rule=SAT_SUN_TO_NEXT_MON_TUE)

        self._add_observed(
            # Boxing Day.
            self._add_christmas_day_two(tr("Boxing Day")),
            rule=SAT_SUN_TO_NEXT_MON_TUE,
        )

    def _populate_optional_holidays(self):
        self._add_observed(
            # Christmas Day.
            self._add_christmas_day(tr("Christmas Day")),
            rule=SAT_SUN_TO_NEXT_MON_TUE,
        )

        self._add_observed(
            # Boxing Day.
            self._add_christmas_day_two(tr("Boxing Day")),
            rule=SAT_SUN_TO_NEXT_MON_TUE,
        )

    def _add_thanksgiving_day(self) -> None:
        """Adds Thanksgiving Day / Armistice Day.

        In 1921, Thanksgiving Day was moved to "Armistice Day" (1st Monday in the week of Nov 11).
        "Remembrance Day" and "Thanksgiving Day" split again in 1931, with Thanksgiving usually on
        the 2nd Monday of October — except in 1935, when it was delayed 10 days due to a General
        Election. It was finally fixed to the 2nd Monday of October permanently in 1957.
        """
        if self._year >= 1931:
            # Thanksgiving Day.
            name = tr("Thanksgiving Day")
            if self._year == 1935:
                self._add_holiday_oct_24(name)
            else:
                self._add_holiday_2nd_mon_of_oct(name)
        elif self._year >= 1921:
            # Armistice Day.
            self._add_holiday_1st_mon_before_nov_12(tr("Armistice Day"))

    def _add_victoria_day(self) -> None:
        """Adds Victoria Day.

        After Queen Victoria's death in 1901, an act of the Canadian Parliament established
        Victoria Day as a legal holiday, to be celebrated on May 24 (or on May 25 when
        May 24 fell on a Sunday). This was later moved to the Monday preceding May 24 in 1952.
        """
        # Victoria Day.
        name = tr("Victoria Day")
        if self._year >= 1953:
            self._add_holiday_1st_mon_before_may_24(name)
        elif self._year >= 1901:
            self._add_observed(self._add_holiday_may_24(name), rule=SUN_TO_NEXT_MON)

    def _populate_subdiv_ab_public_holidays(self):
        if self._year >= 1990:
            # Family Day.
            self._add_holiday_3rd_mon_of_feb(tr("Family Day"))

        self._add_victoria_day()

        if self._year >= 1879:
            self._add_observed(self._canada_day, rule=SUN_TO_NEXT_MON)

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_ab_optional_holidays(self):
        # Easter Monday.
        self._add_easter_monday(tr("Easter Monday"))

        # https://en.wikipedia.org/wiki/Civic_Holiday#Alberta
        if self._year >= 1974:
            # Heritage Day.
            self._add_holiday_1st_mon_of_aug(tr("Heritage Day"))

        if self._year >= 2021:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        # Boxing Day.
        self._add_christmas_day_two(tr("Boxing Day"))

    def _populate_subdiv_bc_public_holidays(self):
        if self._year >= 2013:
            # Family Day.
            name = tr("Family Day")
            if self._year >= 2019:
                self._add_holiday_3rd_mon_of_feb(name)
            else:
                self._add_holiday_2nd_mon_of_feb(name)

        self._add_victoria_day()

        if self._year >= 1879:
            self._add_observed(self._canada_day, rule=SUN_TO_NEXT_MON)

        # https://en.wikipedia.org/wiki/Civic_Holiday#British_Columbia
        if self._year >= 1974:
            # British Columbia Day.
            self._add_holiday_1st_mon_of_aug(tr("British Columbia Day"))

        if self._year >= 2023:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_remembrance_day(tr("Remembrance Day"))

    def _populate_subdiv_mb_public_holidays(self):
        if self._year >= 2008:
            # Louis Riel Day.
            self._add_holiday_3rd_mon_of_feb(tr("Louis Riel Day"))

        self._add_victoria_day()

        self._add_thanksgiving_day()

    def _populate_subdiv_mb_optional_holidays(self):
        if self._year >= 1900:
            self._add_holiday_1st_mon_of_aug(
                # Terry Fox Day.
                tr("Terry Fox Day")
                if self._year >= 2015
                # Civic Holiday.
                else tr("Civic Holiday")
            )

        if self._year >= 1931:
            # Remembrance Day.
            self._add_remembrance_day(tr("Remembrance Day"))

    def _populate_subdiv_nb_public_holidays(self):
        if self._year >= 2018:
            # Family Day.
            self._add_holiday_3rd_mon_of_feb(tr("Family Day"))

        # https://en.wikipedia.org/wiki/Civic_Holiday#New_Brunswick
        if self._year >= 1975:
            # New Brunswick Day.
            self._add_holiday_1st_mon_of_aug(tr("New Brunswick Day"))

        if self._year >= 1931:
            # Remembrance Day.
            self._add_remembrance_day(tr("Remembrance Day"))

    def _populate_subdiv_nb_optional_holidays(self):
        self._add_victoria_day()

        self._add_thanksgiving_day()

        # Boxing Day.
        self._add_christmas_day_two(tr("Boxing Day"))

    def _populate_subdiv_nl_public_holidays(self):
        if self._year >= 1917:
            # Memorial Day.
            self._add_holiday_jul_1(tr("Memorial Day"))

        if self._year >= 1879:
            self._add_observed(self._canada_day)

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_nl_optional_holidays(self):
        if self._year >= 1900:
            # Saint Patrick's Day.
            self._add_holiday(tr("Saint Patrick's Day"), self._get_nearest_monday(MAR, 17))

        if self._year >= 1990:
            # Nearest Monday to April 23
            # 4/26 is the Monday closer to 4/23 in 2010
            # but the holiday was observed on 4/19? Crazy Newfies!
            dt = date(2010, APR, 19) if self._year == 2010 else self._get_nearest_monday(APR, 23)
            # Saint George's Day.
            self._add_holiday(tr("Saint George's Day"), dt)

        if self._year >= 1997:
            # Discovery Day.
            self._add_holiday(tr("Discovery Day"), self._get_nearest_monday(JUN, 24))

        if self._year >= 1900:
            # Orangemen's Day.
            self._add_holiday(tr("Orangemen's Day"), self._get_nearest_monday(JUL, 12))

        self._add_thanksgiving_day()

        # Boxing Day.
        self._add_christmas_day_two(tr("Boxing Day"))

    def _populate_subdiv_ns_public_holidays(self):
        # http://novascotia.ca/lae/employmentrights/NovaScotiaHeritageDay.asp
        if self._year >= 2015:
            # Heritage Day.
            self._add_holiday_3rd_mon_of_feb(tr("Heritage Day"))

        if self._year >= 1981:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_ns_optional_holidays(self):
        if self._year >= 1996:
            # Natal Day.
            self._add_holiday_1st_mon_of_aug(tr("Natal Day"))

    def _populate_subdiv_nt_public_holidays(self):
        self._add_victoria_day()

        if self._year >= 1996:
            # National Aboriginal Day.
            self._add_holiday_jun_21(tr("National Aboriginal Day"))

        if self._year >= 1900:
            # Civic Holiday.
            self._add_holiday_1st_mon_of_aug(tr("Civic Holiday"))

        if self._year >= 2022:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_remembrance_day(tr("Remembrance Day"))

    def _populate_subdiv_nu_public_holidays(self):
        self._add_victoria_day()

        if self._year >= 2020:
            # Nunavut Day.
            self._add_holiday_jul_9(tr("Nunavut Day"))

        if self._year >= 1900:
            # Civic Holiday.
            self._add_holiday_1st_mon_of_aug(tr("Civic Holiday"))

        if self._year >= 2022:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_remembrance_day(tr("Remembrance Day"))

    def _populate_subdiv_nu_optional_holidays(self):
        if 2000 <= self._year <= 2019:
            # Nunavut Day.
            name = tr("Nunavut Day")
            if self._year == 2000:
                self._add_holiday_apr_1(name)
            else:
                self._add_holiday_jul_9(name)

    def _populate_subdiv_on_public_holidays(self):
        if self._year >= 2008:
            # Family Day.
            self._add_holiday_3rd_mon_of_feb(tr("Family Day"))

        self._add_victoria_day()

        self._add_thanksgiving_day()

        # Boxing Day.
        self._add_observed(self._add_christmas_day_two(tr("Boxing Day")), rule=SUN_TO_NEXT_TUE)

    def _populate_subdiv_on_optional_holidays(self):
        if self._year >= 1900:
            # Civic Holiday.
            self._add_holiday_1st_mon_of_aug(tr("Civic Holiday"))

    def _populate_subdiv_pe_public_holidays(self):
        if self._year >= 2009:
            # Islander Day.
            name = tr("Islander Day")
            if self._year >= 2010:
                self._add_holiday_3rd_mon_of_feb(name)
            else:
                self._add_holiday_2nd_mon_of_feb(name)

        if self._year >= 1879:
            self._add_observed(self._canada_day)

        if self._year >= 2022:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_qc_public_holidays(self):
        if self._year >= 2003:
            # National Patriots' Day.
            self._add_holiday_1st_mon_before_may_24(tr("National Patriots' Day"))

        if self._year >= 1925:
            self._add_observed(
                # Saint John the Baptist Day.
                self._add_saint_johns_day(tr("Saint Jean Baptiste Day")),
                rule=SUN_TO_NEXT_MON,
            )

        if self._year >= 1879:
            self._add_observed(self._canada_day, rule=SUN_TO_NEXT_MON)

        self._add_thanksgiving_day()

    def _populate_subdiv_qc_optional_holidays(self):
        # Easter Monday.
        self._add_easter_monday(tr("Easter Monday"))

    def _populate_subdiv_sk_public_holidays(self):
        if self._year >= 2007:
            # Family Day.
            self._add_holiday_3rd_mon_of_feb(tr("Family Day"))

        self._add_victoria_day()

        if self._year >= 1879:
            self._add_observed(self._canada_day)

        # https://en.wikipedia.org/wiki/Civic_Holiday#Saskatchewan
        if self._year >= 1900:
            # Saskatchewan Day.
            self._add_holiday_1st_mon_of_aug(tr("Saskatchewan Day"))

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_yt_public_holidays(self):
        self._add_victoria_day()

        if self._year >= 2017:
            # National Aboriginal Day.
            self._add_holiday_jun_21(tr("National Aboriginal Day"))

        if self._year >= 1879:
            self._add_observed(self._canada_day)

        if self._year >= 1912:
            # Discovery Day.
            self._add_holiday_3rd_mon_of_aug(tr("Discovery Day"))

        if self._year >= 2023:
            # National Day for Truth and Reconciliation.
            self._add_holiday_sep_30(tr("National Day for Truth and Reconciliation"))

        self._add_thanksgiving_day()

        if self._year >= 1931:
            # Remembrance Day.
            self._add_observed(self._add_remembrance_day(tr("Remembrance Day")))

    def _populate_subdiv_yt_optional_holidays(self):
        # Friday before the last Sunday in February
        if self._year >= 1976:
            # Heritage Day.
            self._add_holiday_2_days_prior_last_sun_of_feb(tr("Heritage Day"))


class CA(Canada):
    pass


class CAN(Canada):
    pass


class CanadaStaticHolidays:
    # Funeral of Queen Elizabeth II.
    queen_funeral = tr("Funeral of Her Majesty the Queen Elizabeth II")

    special_bc_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }

    special_nb_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }

    special_nl_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }

    special_ns_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }

    special_pe_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }

    special_yt_public_holidays = {
        2022: (SEP, 19, queen_funeral),
    }
