#!/usr/bin/env python3
"""Validate the offline DLR Development Lifecycle extraction artifacts."""

from __future__ import annotations

import csv
import subprocess
import sys
from decimal import Decimal
from pathlib import Path


BASE_DIR = Path(__file__).resolve().parent
EXTRACTOR = BASE_DIR / "extract_dlr_development_lifecycle.py"
MANIFEST = BASE_DIR / "dlr_development_lifecycle_manifest.csv"
OUTPUT = BASE_DIR / "dlr_development_lifecycle_rows.csv"
REPORT = BASE_DIR / "dlr_development_lifecycle_validation_report.md"

REQUIRED_FIELDS = [
    "company",
    "period",
    "metric_name",
    "raw_value",
    "normalized_value",
    "unit",
    "value_qualifier",
    "ownership_basis",
    "forecast_or_actual",
    "is_derived",
    "source_url",
    "source_location",
    "source_snippet",
    "snippet_support_status",
    "confidence",
    "caveat",
]

EXPECTED_PERIODS = ["2Q24", "3Q24", "4Q24", "1Q25", "2Q25", "3Q25", "4Q25", "1Q26"]
EXPECTED_METRICS = [
    "future_land_mw",
    "future_shell_mw",
    "construction_underway_mw",
    "preleased_percentage",
    "development_lifecycle_total_investment_100_usd_m",
]


def fail(message: str) -> None:
    raise AssertionError(message)


def read_csv(path: Path) -> list[dict[str, str]]:
    with path.open(newline="", encoding="utf-8") as handle:
        return list(csv.DictReader(handle))


def as_decimal(value: str) -> Decimal:
    return Decimal(value.replace(",", "").replace("$", "").replace("%", ""))


def validate() -> list[str]:
    subprocess.run([sys.executable, str(EXTRACTOR)], cwd=BASE_DIR, check=True)

    manifest_rows = read_csv(MANIFEST)
    rows = read_csv(OUTPUT)
    checks: list[str] = []

    if len(manifest_rows) != 8:
        fail(f"manifest row count expected 8, got {len(manifest_rows)}")
    checks.append("manifest has one compact source row per quarter")

    if len(rows) != 40:
        fail(f"canonical row count expected 40, got {len(rows)}")
    checks.append("canonical output has exactly 40 rows")

    if rows and list(rows[0].keys()) != REQUIRED_FIELDS:
        fail("canonical schema does not match required field order")
    checks.append("canonical schema and field order match required DLR schema")

    periods = sorted({row["period"] for row in rows}, key=EXPECTED_PERIODS.index)
    if periods != EXPECTED_PERIODS:
        fail(f"period coverage mismatch: {periods}")
    checks.append("all eight quarters from 2Q24 through 1Q26 are present")

    for period in EXPECTED_PERIODS:
        metrics = [row["metric_name"] for row in rows if row["period"] == period]
        if metrics != EXPECTED_METRICS:
            fail(f"{period} metric set/order mismatch: {metrics}")
    checks.append("each quarter has exactly the five Development Lifecycle metrics")

    for row in rows:
        for field in REQUIRED_FIELDS:
            if row[field] == "":
                fail(f"missing {field} in {row['period']} {row['metric_name']}")
        if not row["source_url"].startswith(("https://investor.digitalrealty.com/", "https://www.sec.gov/")):
            fail(f"unexpected source URL: {row['source_url']}")
        if row["snippet_support_status"] not in {"partial", "exact"}:
            fail(f"unsupported snippet status: {row['snippet_support_status']}")
        if row["confidence"] != "high":
            fail(f"expected high confidence: {row}")
    checks.append("source URL, source location, snippet, confidence, and caveat are populated")
    checks.append("snippet support statuses are explicit and supported")

    for row in rows:
        if row["metric_name"] == "preleased_percentage":
            if not row["raw_value"].endswith("%"):
                fail(f"preleased raw value missing percent sign: {row}")
            if as_decimal(row["raw_value"]) != as_decimal(row["normalized_value"]):
                fail(f"preleased normalized value mismatch: {row}")
            if row["unit"] != "percent":
                fail(f"preleased unit mismatch: {row}")
    checks.append("percent parsing strips '%' and preserves percent units")

    for row in rows:
        if row["metric_name"] == "development_lifecycle_total_investment_100_usd_m":
            expected = as_decimal(row["raw_value"]) / Decimal("1000")
            actual = as_decimal(row["normalized_value"])
            if expected != actual:
                fail(f"USD thousands to USD millions conversion mismatch: {row}")
            if row["unit"] != "USD_millions":
                fail(f"investment unit mismatch: {row}")
            if row["ownership_basis"] != "total_100_share":
                fail(f"investment ownership basis mismatch: {row}")
    checks.append("development investment converts source USD thousands to USD millions")

    for row in rows:
        if row["metric_name"] != "development_lifecycle_total_investment_100_usd_m":
            if as_decimal(row["raw_value"]) != as_decimal(row["normalized_value"]):
                fail(f"MW/percent normalized value mismatch: {row}")
    checks.append("MW metrics preserve source values apart from comma normalization")

    REPORT.write_text(
        "# DLR Development Lifecycle Validation Report\n\n"
        "This validator executes the offline extractor against a verified manifest; "
        "it does not fetch or parse live PDFs.\n\n"
        "## Result\n\nPASS\n\n"
        "## Checks\n\n"
        + "\n".join(f"- {check}." for check in checks)
        + "\n",
        encoding="utf-8",
    )
    return checks


def main() -> None:
    validate()
    print("PASS: DLR Development Lifecycle extractor produced 40 validated rows.")


if __name__ == "__main__":
    main()
