from __future__ import annotations

import re
from datetime import timedelta

import numpy as np

from cognitive_signal.dissonance.detector import DissonanceVector
from cognitive_signal.data.generate_test_data import (
    DEFAULT_START_DATE,
    generate_synthetic_entries,
)
from cognitive_signal.models.types import (
    ChangePointOutput,
    Phase,
    JournalEntry,
    SegmentationOutput,
    SignalDetectionOutput,
    SignalVector,
    TemporalSignal,
)
from cognitive_signal.semantic.motion import SemanticMotionOutput


def test_generate_60_entries_with_daily_timestamps() -> None:
    entries = generate_synthetic_entries()
    assert len(entries) == 60
    assert entries[0].timestamp == DEFAULT_START_DATE
    for previous, current in zip(entries, entries[1:]):
        assert current.timestamp - previous.timestamp == timedelta(days=1)


def test_generated_entries_are_non_empty_strings() -> None:
    entries = generate_synthetic_entries()
    for entry in entries:
        assert isinstance(entry.text, str)
        assert entry.text.strip() != ""


def test_generation_is_deterministic_for_defaults() -> None:
    first = generate_synthetic_entries()
    second = generate_synthetic_entries()
    assert [entry.entry_id for entry in first] == [entry.entry_id for entry in second]
    assert [entry.timestamp for entry in first] == [entry.timestamp for entry in second]
    assert [entry.text for entry in first] == [entry.text for entry in second]


def test_generated_entries_are_journal_entries_with_sentence_structure() -> None:
    entries = generate_synthetic_entries()
    for entry in entries:
        assert isinstance(entry, JournalEntry)
        assert any(mark in entry.text for mark in ".!?")
        sentence_parts = [part.strip() for part in re.split(r"[.!?]+", entry.text) if part.strip()]
        assert len(sentence_parts) >= 3


def test_all_dataclasses_instantiate_correctly() -> None:
    entry = JournalEntry(
        text="I sat in silence and reflected.",
        timestamp=DEFAULT_START_DATE,
        entry_id="entry_001",
    )
    signal_vector = SignalVector(
        embedding=np.zeros(8),
        valence=0.2,
        arousal=0.4,
        agency=0.7,
        self_focus=0.6,
        social_orientation=0.3,
        action_ratio=0.5,
    )
    temporal_signal = TemporalSignal(
        delta=0.1,
        trend=-0.2,
        volatility=0.3,
        persistence=0.8,
    )
    dissonance = DissonanceVector(
        protest_positivity=0.1,
        passive_agency_talk=0.2,
        social_isolation_conflict=0.3,
        forced_calm=0.0,
        overall_dissonance=0.15,
    )
    semantic_motion = SemanticMotionOutput(
        step_distances=[0.0],
        reduced_embeddings=np.zeros((1, 3), dtype=np.float32),
        drift_vectors=[np.zeros(3, dtype=np.float32)],
        identity_shifts=[],
    )
    segmentation = SegmentationOutput(
        state_sequence=[0],
        transition_matrix=np.array([[1.0]], dtype=np.float32),
        state_means=np.zeros((1, 11), dtype=np.float32),
        state_covariances=np.zeros((1, 11, 11), dtype=np.float32),
        log_likelihood=-1.0,
    )
    changepoints = ChangePointOutput(
        signal_changepoints={"valence": [1]},
        semantic_changepoints=[],
        merged_changepoints=[1],
    )
    phases = [Phase(start=0, end=0, phase_id=0)]

    output = SignalDetectionOutput(
        journal_entries=[entry],
        signal_vectors=[signal_vector],
        dissonance_vectors=[dissonance],
        temporal_signals={"valence": [temporal_signal]},
        semantic_motion=semantic_motion,
        segmentation=segmentation,
        state_labels={0: "neutral-state"},
        changepoints=changepoints,
        phases=phases,
        _raw_observations=np.zeros((1, 11), dtype=np.float32),
        _raw_feature_names=["f1"],
        _raw_processing_seconds=0.01,
    )

    assert output.journal_entries[0].entry_id == "entry_001"
    assert output.signal_vectors[0].embedding.shape == (8,)
    assert output.temporal_signals["valence"][0].persistence == 0.8
    assert output.dissonance_vectors[0].overall_dissonance == 0.15
    assert output.state_labels[0] == "neutral-state"
