"""Validation logic for raw ingestion artifacts."""

from __future__ import annotations

import csv
import logging
from dataclasses import asdict, dataclass
from datetime import date
from pathlib import Path
from typing import Any

from src.config import IngestionPaths
from src.utils.io import read_csv_header, read_json, write_json

LOGGER = logging.getLogger(__name__)

DATE_ONLY_FIELDS = (
    "birth_datetime",
    "visit_start_date",
    "visit_end_date",
    "measurement_Date",
    "flu_last_administered",
    "tdap_last_administered",
    "mmr_last_administered",
    "polio_last_administered",
)
REQUIRED_COLUMNS = (
    "person_id",
    "visit_occurrence_id",
    "visit_start_date",
    "visit_end_date",
    "visit_type",
    "condition",
    "deceased",
)
COVID_ERA_START = date(2020, 1, 1)


@dataclass(frozen=True)
class ValidationIssue:
    """A validation issue found during raw dataset review."""

    code: str
    severity: str
    message: str


def _parse_iso_date(raw_value: str) -> date | None:
    if not raw_value:
        return None
    return date.fromisoformat(raw_value)


def _metadata_column_names(metadata: dict[str, Any]) -> list[str]:
    return [variable["name"] for variable in metadata["variables"]]


def _date_field_has_time_component(value: str) -> bool:
    return "T" in value or " " in value


def validate_raw_dataset(paths: IngestionPaths | None = None) -> dict[str, Any]:
    """Validate the raw dataset against the Phase 2 ingestion spec.

    Args:
        paths: Project ingestion paths. Defaults to standard project locations.

    Returns:
        A serializable validation report.
    """
    active_paths = paths or IngestionPaths()
    metadata = read_json(active_paths.raw_metadata)
    csv_header = read_csv_header(active_paths.raw_csv)
    metadata_columns = _metadata_column_names(metadata)
    issues: list[ValidationIssue] = []

    row_count = 0
    visit_ids: set[str] = set()
    duplicate_visit_count = 0
    missing_required_counts = {column: 0 for column in REQUIRED_COLUMNS}
    end_before_start_count = 0
    same_day_visit_count = 0
    pre_2020_visit_count = 0
    pre_2020_measurement_count = 0
    non_suspected_covid_with_observation = 0
    missing_observation_count = 0
    condition_examples: set[str] = set()
    date_only_violations = {column: 0 for column in DATE_ONLY_FIELDS}

    with active_paths.raw_csv.open("r", encoding="utf-8", newline="") as handle:
        reader = csv.DictReader(handle)
        for row in reader:
            row_count += 1
            visit_id = row["visit_occurrence_id"]
            if visit_id in visit_ids:
                duplicate_visit_count += 1
            visit_ids.add(visit_id)

            for column in REQUIRED_COLUMNS:
                if not row[column]:
                    missing_required_counts[column] += 1

            start_date = _parse_iso_date(row["visit_start_date"])
            end_date = _parse_iso_date(row["visit_end_date"])
            measurement_date = _parse_iso_date(row["measurement_Date"])

            if start_date and start_date < COVID_ERA_START:
                pre_2020_visit_count += 1
            if measurement_date and measurement_date < COVID_ERA_START:
                pre_2020_measurement_count += 1
            if start_date and end_date:
                if end_date < start_date:
                    end_before_start_count += 1
                if end_date == start_date:
                    same_day_visit_count += 1

            observation = row["observation_source"]
            if not observation:
                missing_observation_count += 1
            elif observation != "Suspected COVID-19":
                non_suspected_covid_with_observation += 1

            condition_value = row["condition"]
            if ":" in condition_value and len(condition_examples) < 10:
                condition_examples.add(condition_value)

            for column in DATE_ONLY_FIELDS:
                value = row[column]
                if value and _date_field_has_time_component(value):
                    date_only_violations[column] += 1

    if csv_header != metadata_columns:
        issues.append(
            ValidationIssue(
                code="header_metadata_mismatch",
                severity="warning",
                message="CSV header does not exactly match metadata.json column names.",
            )
        )
    if duplicate_visit_count > 0:
        issues.append(
            ValidationIssue(
                code="duplicate_visit_occurrence_id",
                severity="error",
                message="visit_occurrence_id is not unique at the row level.",
            )
        )
    if end_before_start_count > 0:
        issues.append(
            ValidationIssue(
                code="visit_end_before_start",
                severity="error",
                message="Some visits end before they start.",
            )
        )
    if pre_2020_visit_count > 0 or pre_2020_measurement_count > 0:
        issues.append(
            ValidationIssue(
                code="implausible_pre_covid_dates",
                severity="warning",
                message="The raw file includes visit or measurement dates before 2020-01-01.",
            )
        )
    if missing_observation_count > 0 or non_suspected_covid_with_observation > 0:
        issues.append(
            ValidationIssue(
                code="broad_cohort_scope",
                severity="warning",
                message="The raw file is broader than a clean COVID-only cohort.",
            )
        )
    if any(date_only_violations.values()):
        issues.append(
            ValidationIssue(
                code="unexpected_timestamp_values",
                severity="warning",
                message="At least one documented date-only field contains a time component.",
            )
        )

    report = {
        "source_file": str(active_paths.raw_csv),
        "metadata_file": str(active_paths.raw_metadata),
        "row_count": row_count,
        "column_count": len(csv_header),
        "csv_header": csv_header,
        "metadata_columns": metadata_columns,
        "header_matches_metadata": csv_header == metadata_columns,
        "required_columns_present": {
            column: column in csv_header for column in REQUIRED_COLUMNS
        },
        "duplicate_visit_occurrence_id_count": duplicate_visit_count,
        "missing_required_counts": missing_required_counts,
        "visit_end_before_start_count": end_before_start_count,
        "same_day_visit_count": same_day_visit_count,
        "pre_2020_visit_count": pre_2020_visit_count,
        "pre_2020_measurement_count": pre_2020_measurement_count,
        "missing_observation_source_count": missing_observation_count,
        "non_suspected_covid_observation_count": non_suspected_covid_with_observation,
        "date_only_violations": date_only_violations,
        "encounter_identifier_assumption_validated": duplicate_visit_count == 0,
        "condition_examples_with_multiple_terms": sorted(condition_examples),
        "issues": [asdict(issue) for issue in issues],
    }
    return report


def write_validation_report(paths: IngestionPaths | None = None) -> dict[str, Any]:
    """Run validation and persist the JSON report to disk."""
    active_paths = paths or IngestionPaths()
    report = validate_raw_dataset(active_paths)
    write_json(active_paths.validation_report, report)
    LOGGER.info("Wrote validation report to %s", active_paths.validation_report)
    return report


if __name__ == "__main__":
    from src.logging_config import configure_logging

    configure_logging()
    write_validation_report()
