"""Comprehensive dateparser tests replicating how CCA uses dateparser.

These tests verify that dateparser behaves correctly for all the date formats,
timezone patterns, settings combinations, and edge cases used across the CCA
codebase (spiders, extractors, date_processor, validators, etc.).
"""

from datetime import datetime, timezone

import dateparser
import pytest


# ---------------------------------------------------------------------------
# ISO formats
# ---------------------------------------------------------------------------

class TestISOFormats:

    def test_iso_date_only(self):
        result = dateparser.parse("2025-02-06")
        assert result is not None
        assert result.year == 2025
        assert result.month == 2
        assert result.day == 6

    def test_iso_with_time(self):
        result = dateparser.parse("2025-02-06T00:00:00")
        assert result is not None
        assert result.year == 2025
        assert result.hour == 0

    def test_iso_with_time_and_offset(self):
        result = dateparser.parse("2025-02-06T15:30:00-05:00")
        assert result is not None
        assert result.hour == 15
        assert result.minute == 30
        assert result.tzinfo is not None

    def test_iso_with_zulu(self):
        result = dateparser.parse("2025-02-06T21:00:00Z")
        assert result is not None
        assert result.hour == 21
        assert result.tzinfo is not None

    def test_iso_datetime_space_separated(self):
        result = dateparser.parse("2025-02-06 14:30:45")
        assert result is not None
        assert result.hour == 14
        assert result.minute == 30
        assert result.second == 45

    def test_iso_week_date(self):
        result = dateparser.parse("2025-W05")
        assert result is not None
        assert result.year == 2025


# ---------------------------------------------------------------------------
# US / text date formats
# ---------------------------------------------------------------------------

class TestUSDateFormats:

    def test_mm_dd_yyyy_slash(self):
        result = dateparser.parse("02/06/2025")
        assert result is not None
        assert result.month == 2
        assert result.day == 6

    def test_yyyy_mm_dd_slash(self):
        result = dateparser.parse("2025/12/25")
        assert result is not None
        assert result.month == 12
        assert result.day == 25

    def test_full_month_name(self):
        result = dateparser.parse("February 6, 2025")
        assert result is not None
        assert result.month == 2
        assert result.day == 6

    def test_abbreviated_month(self):
        result = dateparser.parse("Feb 6, 2025")
        assert result is not None
        assert result.month == 2
        assert result.day == 6

    def test_day_first_text(self):
        result = dateparser.parse("6 Feb 2025")
        assert result is not None
        assert result.day == 6
        assert result.month == 2

    def test_month_year_only(self):
        result = dateparser.parse("March 2025")
        assert result is not None
        assert result.month == 3
        assert result.year == 2025

    def test_abbreviated_month_year(self):
        result = dateparser.parse("Mar 2025")
        assert result is not None
        assert result.month == 3
        assert result.year == 2025

    def test_month_day_no_comma(self):
        result = dateparser.parse("Jan 15 2025")
        assert result is not None
        assert result.month == 1
        assert result.day == 15


# ---------------------------------------------------------------------------
# Separator variants
# ---------------------------------------------------------------------------

class TestSeparators:

    def test_dash_separator(self):
        result = dateparser.parse("2025-12-25")
        assert result is not None
        assert result.month == 12

    def test_slash_separator(self):
        result = dateparser.parse("2025/12/25")
        assert result is not None
        assert result.month == 12

    def test_dot_separator(self):
        result = dateparser.parse("2025.02.06")
        assert result is not None
        assert result.month == 2

    def test_space_separator(self):
        result = dateparser.parse("2025 12 25")
        assert result is not None
        assert result.month == 12


# ---------------------------------------------------------------------------
# Ordinal date formats
# ---------------------------------------------------------------------------

class TestOrdinalDates:

    def test_jan_1st(self):
        result = dateparser.parse("Jan 1st, 2025")
        assert result is not None
        assert result.month == 1
        assert result.day == 1

    def test_1st_january(self):
        result = dateparser.parse("1st January 2025")
        assert result is not None
        assert result.day == 1

    def test_2nd_march(self):
        result = dateparser.parse("2nd March 2025")
        assert result is not None
        assert result.day == 2
        assert result.month == 3

    def test_3rd_april(self):
        result = dateparser.parse("3rd April 2025")
        assert result is not None
        assert result.day == 3

    def test_21st_december(self):
        result = dateparser.parse("21st December 2025")
        assert result is not None
        assert result.day == 21


# ---------------------------------------------------------------------------
# Date + time formats (as used in extractors/spiders)
# ---------------------------------------------------------------------------

class TestDateTimeFormats:

    def test_24_hour_time(self):
        result = dateparser.parse("2025-02-06 23:59:59")
        assert result is not None
        assert result.hour == 23
        assert result.minute == 59

    def test_12_hour_pm(self):
        result = dateparser.parse("February 6, 2025 11:30 PM")
        assert result is not None
        assert result.hour == 23
        assert result.minute == 30

    def test_12_hour_am(self):
        result = dateparser.parse("February 6, 2025 11:30 AM")
        assert result is not None
        assert result.hour == 11
        assert result.minute == 30

    def test_date_time_with_timezone_abbreviation(self):
        result = dateparser.parse("January 12, 2025 10:00 PM EST")
        assert result is not None
        assert result.hour == 22

    def test_spider_date_format(self):
        """Format used by PRNewsWire spider: Apr 07, 2021, 16:08 ET"""
        result = dateparser.parse("Apr 07, 2021, 16:08 ET")
        assert result is not None
        assert result.year == 2021
        assert result.month == 4
        assert result.day == 7

    def test_iso_with_am_pm_and_tz(self):
        """Format seen in scraper tests: 2026-01-27 10:28 AM EST"""
        result = dateparser.parse("2026-01-27 10:28 AM EST")
        assert result is not None
        assert result.year == 2026
        assert result.hour == 10
        assert result.minute == 28


# ---------------------------------------------------------------------------
# Timezone-aware parsing (TIMEZONE + RETURN_AS_TIMEZONE_AWARE settings)
# ---------------------------------------------------------------------------

class TestTimezoneSettings:

    def test_us_eastern(self):
        result = dateparser.parse(
            "2025-02-06T15:30:00",
            settings={"TIMEZONE": "US/Eastern", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None
        assert result.hour == 15

    def test_us_pacific(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "US/Pacific", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_us_central(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "US/Central", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_us_mountain(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "US/Mountain", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_gmt(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "GMT", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_utc(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "UTC", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_europe_paris(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "Europe/Paris", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_america_santiago(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "America/Santiago", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_pacific_honolulu(self):
        result = dateparser.parse(
            "2025-02-06 10:00 AM",
            settings={"TIMEZONE": "Pacific/Honolulu", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None

    def test_naive_date_gets_default_tz(self):
        """Replicates extractor.default_timezone() -- naive dates get US/Eastern."""
        naive_str = "2020-02-13T00:00:00"
        result = dateparser.parse(naive_str)
        assert result is not None
        assert result.tzinfo is None  # no tz by default

        aware = dateparser.parse(
            naive_str,
            settings={"TIMEZONE": "US/Eastern", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert aware is not None
        assert aware.tzinfo is not None

    def test_already_aware_date_preserved(self):
        """If the string already has offset info, dateparser keeps it."""
        result = dateparser.parse("2025-02-06T15:30:00-05:00")
        assert result is not None
        assert result.tzinfo is not None


# ---------------------------------------------------------------------------
# DATE_ORDER settings (used in integration tests)
# ---------------------------------------------------------------------------

class TestDateOrderSettings:

    def test_dmy_order(self):
        result = dateparser.parse("12/01/2025", settings={"DATE_ORDER": "DMY"})
        assert result is not None
        assert result.day == 12
        assert result.month == 1

    def test_mdy_order(self):
        result = dateparser.parse("12/01/2025", settings={"DATE_ORDER": "MDY"})
        assert result is not None
        assert result.month == 12
        assert result.day == 1

    def test_ymd_order(self):
        result = dateparser.parse("2025/01/12", settings={"DATE_ORDER": "YMD"})
        assert result is not None
        assert result.year == 2025
        assert result.month == 1
        assert result.day == 12


# ---------------------------------------------------------------------------
# Relative dates (used in tests and chat modules)
# ---------------------------------------------------------------------------

class TestRelativeDates:

    BASE = datetime(2025, 2, 6, 12, 0, 0)

    def test_tomorrow(self):
        result = dateparser.parse("tomorrow", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 7

    def test_yesterday(self):
        result = dateparser.parse("yesterday", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 5

    def test_in_2_weeks(self):
        result = dateparser.parse("in 2 weeks", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 20

    def test_3_days_ago(self):
        result = dateparser.parse("3 days ago", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 3

    def test_in_5_days(self):
        result = dateparser.parse("in 5 days", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 11

    def test_2_weeks_ago(self):
        result = dateparser.parse("2 weeks ago", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 23
        assert result.month == 1

    def test_next_month(self):
        result = dateparser.parse("next month", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.month == 3

    def test_last_year(self):
        result = dateparser.parse("last year", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.year == 2024

    def test_in_3_hours(self):
        result = dateparser.parse("in 3 hours", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.hour == 15

    def test_5_minutes_ago(self):
        result = dateparser.parse("5 minutes ago", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.hour == 11
        assert result.minute == 55

    def test_last_month(self):
        result = dateparser.parse("last month", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.month == 1

    def test_next_year(self):
        result = dateparser.parse("next year", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.year == 2026

    def test_in_1_week(self):
        result = dateparser.parse("in 1 week", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 13

    def test_30_seconds_ago(self):
        result = dateparser.parse("30 seconds ago", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.minute == 59

    def test_today(self):
        result = dateparser.parse("today", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 6
        assert result.month == 2

    def test_now(self):
        result = dateparser.parse("now", settings={"RELATIVE_BASE": self.BASE})
        assert result is not None
        assert result.day == 6


# ---------------------------------------------------------------------------
# PREFER_DATES_FROM (used for ambiguous day names)
# ---------------------------------------------------------------------------

class TestPreferDatesFrom:

    BASE = datetime(2025, 2, 6, 12, 0, 0)  # a Thursday

    def test_prefer_future(self):
        result = dateparser.parse(
            "Monday",
            settings={"PREFER_DATES_FROM": "future", "RELATIVE_BASE": self.BASE},
        )
        assert result is not None
        assert result >= self.BASE

    def test_prefer_past(self):
        result = dateparser.parse(
            "Monday",
            settings={"PREFER_DATES_FROM": "past", "RELATIVE_BASE": self.BASE},
        )
        assert result is not None
        assert result <= self.BASE

    def test_day_names_parseable(self):
        base_settings = {"RELATIVE_BASE": self.BASE}
        for day in ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]:
            result = dateparser.parse(day, settings=base_settings)
            assert result is not None, f"Failed to parse day name: {day}"


# ---------------------------------------------------------------------------
# STRICT_PARSING
# ---------------------------------------------------------------------------

class TestStrictParsing:

    def test_strict_accepts_explicit_date(self):
        result = dateparser.parse("2025-02-06", settings={"STRICT_PARSING": True})
        assert result is not None

    def test_strict_rejects_ambiguous(self):
        result = dateparser.parse("random text", settings={"STRICT_PARSING": True})
        assert result is None


# ---------------------------------------------------------------------------
# Invalid / garbage input
# ---------------------------------------------------------------------------

class TestInvalidInputs:

    def test_empty_string(self):
        result = dateparser.parse("")
        assert result is None

    def test_not_a_date(self):
        result = dateparser.parse("not a date")
        assert result is None

    def test_random_text_with_numbers(self):
        result = dateparser.parse("random text 123")
        assert result is None


# ---------------------------------------------------------------------------
# Unix timestamps (used in some scraper contexts)
# ---------------------------------------------------------------------------

class TestUnixTimestamps:

    def test_epoch_timestamp(self):
        result = dateparser.parse("1484823450")
        assert result is not None
        assert result.year == 2017
        assert result.month == 1
        assert result.day == 19


# ---------------------------------------------------------------------------
# Edge-case dates
# ---------------------------------------------------------------------------

class TestEdgeCases:

    def test_leap_year(self):
        result = dateparser.parse("February 29, 2024")
        assert result is not None
        assert result.day == 29
        assert result.month == 2

    def test_year_start(self):
        result = dateparser.parse("January 1, 2025")
        assert result is not None
        assert result.month == 1
        assert result.day == 1

    def test_year_end(self):
        result = dateparser.parse("December 31, 2025")
        assert result is not None
        assert result.month == 12
        assert result.day == 31


# ---------------------------------------------------------------------------
# Time-only strings (used in PRNewsWire spider after stripping timezone)
# ---------------------------------------------------------------------------

class TestTimeOnlyStrings:

    def test_24h_time_only(self):
        """PRNewsWire spider parses bare time strings like '09:54'."""
        result = dateparser.parse("09:54")
        assert result is not None
        assert result.hour == 9
        assert result.minute == 54

    def test_12h_time_only(self):
        result = dateparser.parse("8:00 a.m.")
        assert result is not None
        assert result.hour == 8

    def test_time_with_tz_setting(self):
        """Spider pattern: parse time with TIMEZONE setting."""
        result = dateparser.parse(
            "09:54",
            settings={"TIMEZONE": "US/Eastern", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.hour == 9
        assert result.tzinfo is not None


# ---------------------------------------------------------------------------
# Timezone abbreviation parsing (used in dates.py extract_time_and_real_zone)
# ---------------------------------------------------------------------------

class TestTimezoneAbbreviationParsing:
    """Verify dateparser can parse times with US timezone abbreviations.

    CCA's dates.py strips timezone abbreviations and parses the time separately,
    but some formats are passed directly to dateparser.
    """

    def test_time_with_est(self):
        result = dateparser.parse("5:00 PM EST")
        assert result is not None

    def test_time_with_edt(self):
        result = dateparser.parse("5:00 PM EDT")
        assert result is not None

    def test_time_with_pst(self):
        result = dateparser.parse("5:00 PM PST")
        assert result is not None

    def test_time_with_pdt(self):
        result = dateparser.parse("5:00 PM PDT")
        assert result is not None

    def test_time_with_cst(self):
        result = dateparser.parse("5:00 PM CST")
        assert result is not None

    def test_time_with_cdt(self):
        result = dateparser.parse("5:00 PM CDT")
        assert result is not None

    def test_time_with_mst(self):
        result = dateparser.parse("5:00 PM MST")
        assert result is not None

    def test_time_with_gmt(self):
        result = dateparser.parse("6:00 PM GMT")
        assert result is not None

    def test_time_with_utc(self):
        result = dateparser.parse("6:00 PM UTC")
        assert result is not None

    def test_time_with_cet(self):
        result = dateparser.parse("6:00 PM CET")
        assert result is not None


# ---------------------------------------------------------------------------
# Combined settings (TIMEZONE + DATE_ORDER + RETURN_AS_TIMEZONE_AWARE)
# ---------------------------------------------------------------------------

class TestCombinedSettings:

    def test_timezone_and_date_order(self):
        result = dateparser.parse(
            "06/02/2025",
            settings={
                "TIMEZONE": "US/Eastern",
                "RETURN_AS_TIMEZONE_AWARE": True,
                "DATE_ORDER": "MDY",
            },
        )
        assert result is not None
        assert result.month == 6
        assert result.day == 2
        assert result.tzinfo is not None

    def test_timezone_and_date_order_dmy(self):
        result = dateparser.parse(
            "06/02/2025",
            settings={
                "TIMEZONE": "US/Eastern",
                "RETURN_AS_TIMEZONE_AWARE": True,
                "DATE_ORDER": "DMY",
            },
        )
        assert result is not None
        assert result.day == 6
        assert result.month == 2
        assert result.tzinfo is not None


# ---------------------------------------------------------------------------
# is_parsable_date pattern (used in extractor.py)
# ---------------------------------------------------------------------------

class TestIsParsablePattern:
    """Replicates extractor.is_parsable_date() -- just checks parse() is not None."""

    @pytest.mark.parametrize("date_str", [
        "2025-02-06",
        "February 6, 2025",
        "Feb 6, 2025",
        "02/06/2025",
        "2025-02-06T15:30:00-05:00",
        "tomorrow",
        "1484823450",
    ])
    def test_parsable_dates(self, date_str):
        assert dateparser.parse(date_str) is not None

    @pytest.mark.parametrize("date_str", [
        "",
        "not a date",
        "random text 123",
    ])
    def test_unparsable_dates(self, date_str):
        assert dateparser.parse(date_str) is None


# ---------------------------------------------------------------------------
# parse_with_zone pattern (used in extractor.py)
# ---------------------------------------------------------------------------

class TestParseWithZonePattern:
    """Replicates extractor.parse_with_zone() -- parse and convert to isoformat."""

    def test_parse_with_eastern_zone(self):
        result = dateparser.parse(
            "February 6, 2025 5:00 PM",
            settings={"TIMEZONE": "US/Eastern", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        iso = result.isoformat()
        assert "T17:00:00" in iso
        assert "-05:00" in iso

    def test_parse_without_zone(self):
        """When zone is None, parse without settings and call isoformat()."""
        result = dateparser.parse("February 6, 2025 5:00 PM")
        assert result is not None
        iso = result.isoformat()
        assert "T17:00:00" in iso

    def test_parse_with_pacific_zone(self):
        result = dateparser.parse(
            "February 6, 2025 5:00 PM",
            settings={"TIMEZONE": "US/Pacific", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        iso = result.isoformat()
        assert "T17:00:00" in iso
        assert "-08:00" in iso


# ---------------------------------------------------------------------------
# Scraper spider date patterns
# ---------------------------------------------------------------------------

class TestScraperPatterns:
    """Date formats seen in various CCA spider scrapers."""

    def test_prnewswire_format(self):
        """Apr 07, 2021, 16:08 ET"""
        result = dateparser.parse("Apr 07, 2021, 16:08 ET")
        assert result is not None
        assert result.year == 2021
        assert result.month == 4
        assert result.day == 7
        assert result.hour == 16
        assert result.minute == 8

    def test_globenewswire_iso(self):
        result = dateparser.parse("2026-01-26T13:12:00-05:00")
        assert result is not None
        assert result.year == 2026
        assert result.hour == 13
        assert result.tzinfo is not None

    def test_spider_with_timezone_setting(self):
        """Spiders typically parse with TIMEZONE=US/Eastern."""
        result = dateparser.parse(
            "February 6, 2025",
            settings={"TIMEZONE": "US/Eastern", "RETURN_AS_TIMEZONE_AWARE": True},
        )
        assert result is not None
        assert result.tzinfo is not None


# ---------------------------------------------------------------------------
# All months parse correctly (regression guard)
# ---------------------------------------------------------------------------

class TestAllMonths:

    @pytest.mark.parametrize("month_str,expected_month", [
        ("January 15, 2025", 1),
        ("February 15, 2025", 2),
        ("March 15, 2025", 3),
        ("April 15, 2025", 4),
        ("May 15, 2025", 5),
        ("June 15, 2025", 6),
        ("July 15, 2025", 7),
        ("August 15, 2025", 8),
        ("September 15, 2025", 9),
        ("October 15, 2025", 10),
        ("November 15, 2025", 11),
        ("December 15, 2025", 12),
    ])
    def test_full_month_names(self, month_str, expected_month):
        result = dateparser.parse(month_str)
        assert result is not None
        assert result.month == expected_month

    @pytest.mark.parametrize("month_str,expected_month", [
        ("Jan 15, 2025", 1),
        ("Feb 15, 2025", 2),
        ("Mar 15, 2025", 3),
        ("Apr 15, 2025", 4),
        ("May 15, 2025", 5),
        ("Jun 15, 2025", 6),
        ("Jul 15, 2025", 7),
        ("Aug 15, 2025", 8),
        ("Sep 15, 2025", 9),
        ("Oct 15, 2025", 10),
        ("Nov 15, 2025", 11),
        ("Dec 15, 2025", 12),
    ])
    def test_abbreviated_month_names(self, month_str, expected_month):
        result = dateparser.parse(month_str)
        assert result is not None
        assert result.month == expected_month
