#!/usr/bin/env python3
"""Compare regional and global funding for one market, then list its leaders."""

from __future__ import annotations

import argparse
import json
import os
import random
import time
from dataclasses import dataclass
from datetime import datetime, timezone
from email.utils import parsedate_to_datetime
from typing import Any

import requests
from dotenv import load_dotenv


API_BASE = "https://api.beta.dealroom.app"
TOKEN_URL = "https://accounts.beta.dealroom.co/oauth/token"
AUDIENCE = "https://api-next.beta.dealroom.co"
DEFAULT_TAG_ID = 823301
DEFAULT_TAG_NAME = "Quantum Computing"
DEFAULT_LOCATION_ID = 34
DEFAULT_LOCATION_NAME = "Europe"
RETRYABLE_STATUS = {429, 500, 502, 503, 504}


def retry_delay(value: str | None, fallback: float) -> float:
    if not value:
        return fallback
    try:
        seconds = float(value)
    except ValueError:
        try:
            retry_at = parsedate_to_datetime(value)
            if retry_at.tzinfo is None:
                retry_at = retry_at.replace(tzinfo=timezone.utc)
            seconds = (retry_at - datetime.now(timezone.utc)).total_seconds()
        except (TypeError, ValueError, OverflowError):
            return fallback
    return min(max(seconds, 0.0), 30.0)


@dataclass
class DealroomClient:
    client_id: str
    client_secret: str
    user_agent: str
    token: str | None = None

    def authenticate(self) -> None:
        response = requests.post(
            TOKEN_URL,
            json={
                "grant_type": "client_credentials",
                "client_id": self.client_id,
                "client_secret": self.client_secret,
                "audience": AUDIENCE,
            },
            headers={"Accept": "application/json"},
            timeout=30,
        )
        response.raise_for_status()
        self.token = response.json()["access_token"]

    def get(self, path: str, params: dict[str, Any]) -> dict[str, Any]:
        if not self.token:
            self.authenticate()

        last_error: requests.HTTPError | None = None
        for attempt in range(4):
            response = requests.get(
                f"{API_BASE}{path}",
                params=params,
                headers={
                    "Authorization": f"Bearer {self.token}",
                    "X-Client-Id": self.client_id,
                    "User-Agent": self.user_agent,
                    "Accept": "application/json",
                },
                timeout=30,
            )
            if response.ok:
                payload = response.json()
                tier = (payload.get("page") or {}).get("tier")
                if tier or payload.get("locked"):
                    raise RuntimeError(
                        "The API treated this request as a non-M2M caller. "
                        "Check Authorization and X-Client-Id before using the result."
                    )
                return payload
            if response.status_code == 401 and attempt == 0:
                self.authenticate()
                continue
            try:
                response.raise_for_status()
            except requests.HTTPError as error:
                last_error = error
            if response.status_code not in RETRYABLE_STATUS or attempt == 3:
                raise last_error or RuntimeError(response.text)
            delay = retry_delay(
                response.headers.get("Retry-After"), 0.3 * (2**attempt)
            )
            time.sleep(delay + random.uniform(0, 0.2))

        raise last_error or RuntimeError("Dealroom request failed")


def rows(payload: dict[str, Any]) -> list[dict[str, Any]]:
    value = payload.get("data")
    return value if isinstance(value, list) else []


def image_url(value: str | None) -> str | None:
    if not value:
        return None
    return value if value.startswith(("http://", "https://")) else f"https://{value}"


def percentage(numerator: float, denominator: float) -> float | None:
    if not denominator:
        return None
    return round((numerator / denominator) * 100, 1)


def money(value: Any) -> str:
    if value is None:
        return "undisclosed"
    number = float(value)
    if number >= 1_000_000_000:
        return f"€{number / 1_000_000_000:.2f}B"
    if number >= 1_000_000:
        return f"€{number / 1_000_000:.1f}M"
    return f"€{number:,.0f}"


def year_values(payload: dict[str, Any]) -> dict[int, float]:
    return {int(row["year"]): float(row.get("value") or 0) for row in rows(payload)}


def normalize_company(entity: dict[str, Any], rank: int) -> dict[str, Any]:
    company = entity.get("company") or {}
    funding = entity.get("funding_summary") or {}
    valuation = entity.get("latest_valuation") or {}
    return {
        "rank": rank,
        "uuid": entity.get("uuid"),
        "name": entity.get("name"),
        "tagline": entity.get("tagline"),
        "image": image_url(entity.get("image")),
        "website": entity.get("website"),
        "dealroom_url": entity.get("dealroom_url"),
        "hq_city": entity.get("hq_city"),
        "hq_country": entity.get("hq_country"),
        "launch_year": entity.get("launch_year"),
        "employee_count": entity.get("employee_count"),
        "total_funding_eur": company.get("total_funding", funding.get("total_funding")),
        "latest_valuation_eur": valuation.get("value"),
        "signal_rating": company.get("signal_rating"),
        "is_spinout": bool(entity.get("is_spinout")),
        "is_hiring": bool(company.get("is_hiring")),
        "open_jobs_count": company.get("open_jobs_count") or 0,
        "founders": [
            {
                "uuid": founder.get("uuid"),
                "name": founder.get("name"),
                "dealroom_url": founder.get("dealroom_url"),
            }
            for founder in (entity.get("founders") or [])[:3]
        ],
    }


def build_pulse(
    client: DealroomClient,
    tag_id: int,
    tag_name: str,
    location_id: int,
    location_name: str,
    start_year: int,
    end_year: int,
    limit: int,
) -> dict[str, Any]:
    europe_filter = f"and(tag_id[eq]:{tag_id},hq_location[eq]:{location_id})"
    global_filter = f"tag_id[eq]:{tag_id}"
    company_filter = (
        f"and(tag_id[eq]:{tag_id},hq_location[eq]:{location_id},"
        "company_status[eq]:operational)"
    )
    base_timeseries = {
        "metric": "vc_funding",
        "aggregation": "sum",
        "year_min": start_year,
        "year_max": end_year,
        "currency": "EUR",
    }
    europe_query = {**base_timeseries, "filter": europe_filter}
    global_query = {**base_timeseries, "filter": global_filter}
    companies_query = {
        "filter": company_filter,
        "sort": "-total_funding",
        "limit": limit,
        "include_total": "true",
        "currency": "EUR",
    }

    europe_payload = client.get("/analytics/timeseries", europe_query)
    global_payload = client.get("/analytics/timeseries", global_query)
    companies_payload = client.get("/data/companies", companies_query)

    europe_by_year = year_values(europe_payload)
    global_by_year = year_values(global_payload)
    series = []
    for year in range(start_year, end_year + 1):
        regional = europe_by_year.get(year, 0)
        global_value = global_by_year.get(year, 0)
        series.append(
            {
                "year": year,
                "europe_funding_eur": round(regional),
                "global_funding_eur": round(global_value),
                "europe_share_percent": percentage(regional, global_value),
                "period": "ytd" if year == end_year else "full_year",
            }
        )

    companies = [
        normalize_company(company, rank)
        for rank, company in enumerate(rows(companies_payload), start=1)
    ]
    if not companies:
        raise RuntimeError("The company query returned no matches.")

    latest_full_year = end_year - 1
    latest = next(row for row in series if row["year"] == latest_full_year)
    previous = next(row for row in series if row["year"] == latest_full_year - 1)
    current = next(row for row in series if row["year"] == end_year)

    return {
        "generated_at": datetime.now(timezone.utc).isoformat(),
        "source": "Dealroom API early access",
        "api_endpoints": ["/analytics/timeseries", "/data/companies"],
        "currency": europe_payload.get("currency", "EUR"),
        "methodology": (
            "Two Dealroom timeseries calls compare annual VC funding for the regional "
            "and global market. A company-list call adds the funded companies behind "
            "the trend."
        ),
        "market": {
            "name": f"{location_name} {tag_name.lower()}",
            "sector": {"id": tag_id, "name": tag_name, "type": "sector"},
            "location": {"id": location_id, "name": location_name},
            "start_year": start_year,
            "end_year": end_year,
            "latest_full_year": latest_full_year,
        },
        "queries": {
            "europe_timeseries": europe_query,
            "global_timeseries": global_query,
            "top_companies": {**companies_query, "include_total": True},
        },
        "summary": {
            "latest_full_year": latest_full_year,
            "europe_latest_full_year_funding_eur": latest["europe_funding_eur"],
            "europe_previous_full_year_funding_eur": previous["europe_funding_eur"],
            "europe_full_year_growth_percent": percentage(
                latest["europe_funding_eur"] - previous["europe_funding_eur"],
                previous["europe_funding_eur"],
            ),
            "europe_global_share_percent": latest["europe_share_percent"],
            "current_year": end_year,
            "europe_current_year_ytd_funding_eur": current["europe_funding_eur"],
            "global_current_year_ytd_funding_eur": current["global_funding_eur"],
            "european_companies_matched": (companies_payload.get("page") or {}).get(
                "total"
            ),
            "top_companies_returned": len(companies),
        },
        "series": series,
        "companies": companies,
        "limitations": [
            "The current calendar year is year to date.",
            "Undisclosed funding rounds are not included in monetary totals.",
            "The regional cohort uses current headquarters.",
            "Taxonomy membership can change as company profiles are updated.",
            "Funding volume is not a measure of technical progress or company quality.",
        ],
    }


def markdown(pulse: dict[str, Any]) -> str:
    market = pulse["market"]
    summary = pulse["summary"]
    lines = [
        f"# {market['name'].title()} funding pulse",
        "",
        pulse["methodology"],
        "",
        f"- {summary['latest_full_year']} regional funding: {money(summary['europe_latest_full_year_funding_eur'])}",
        f"- Year-over-year growth: {summary['europe_full_year_growth_percent']}%",
        f"- Regional share of global funding: {summary['europe_global_share_percent']}%",
        f"- {summary['current_year']} YTD regional funding: {money(summary['europe_current_year_ytd_funding_eur'])}",
        "",
        "## Annual funding",
        "",
        "| Year | Regional | Global | Regional share |",
        "| --- | ---: | ---: | ---: |",
    ]
    for row in pulse["series"]:
        year = f"{row['year']} YTD" if row["period"] == "ytd" else str(row["year"])
        share = (
            f"{row['europe_share_percent']}%"
            if row["europe_share_percent"] is not None
            else "n/a"
        )
        lines.append(
            f"| {year} | {money(row['europe_funding_eur'])} | "
            f"{money(row['global_funding_eur'])} | {share} |"
        )
    lines.extend(["", "## Top regional companies", ""])
    for company in pulse["companies"]:
        location = ", ".join(
            value for value in [company["hq_city"], company["hq_country"]] if value
        ) or "Location unavailable"
        lines.extend(
            [
                f"### {company['rank']}. {company['name']}",
                company.get("tagline") or "No tagline available.",
                f"- Headquarters: {location}",
                f"- Total funding: {money(company['total_funding_eur'])}",
                f"- Employees: {company['employee_count'] or 'Unavailable'}",
                f"- Dealroom: {company.get('dealroom_url') or 'Not available'}",
                "",
            ]
        )
    return "\n".join(lines)


def parse_args() -> argparse.Namespace:
    current_year = datetime.now(timezone.utc).year
    parser = argparse.ArgumentParser(
        description="Compare regional and global funding for one Dealroom market."
    )
    parser.add_argument("--tag-id", type=int, default=DEFAULT_TAG_ID)
    parser.add_argument("--tag-name", default=DEFAULT_TAG_NAME)
    parser.add_argument("--location-id", type=int, default=DEFAULT_LOCATION_ID)
    parser.add_argument("--location-name", default=DEFAULT_LOCATION_NAME)
    parser.add_argument("--start-year", type=int, default=2018)
    parser.add_argument("--end-year", type=int, default=current_year)
    parser.add_argument("--limit", type=int, default=8)
    parser.add_argument("--json", action="store_true", help="Print structured JSON")
    return parser.parse_args()


def main() -> None:
    load_dotenv()
    args = parse_args()
    if args.start_year >= args.end_year:
        raise SystemExit("--start-year must be earlier than --end-year")
    if not 1 <= args.limit <= 100:
        raise SystemExit("--limit must be between 1 and 100")

    client_id = os.environ.get("DEALROOM_CLIENT_ID")
    client_secret = os.environ.get("DEALROOM_CLIENT_SECRET")
    if not client_id or not client_secret:
        raise SystemExit("Set DEALROOM_CLIENT_ID and DEALROOM_CLIENT_SECRET in .env")

    client = DealroomClient(
        client_id=client_id,
        client_secret=client_secret,
        user_agent=os.environ.get("DEALROOM_USER_AGENT", "your-market-pulse/1.0"),
    )
    pulse = build_pulse(
        client=client,
        tag_id=args.tag_id,
        tag_name=args.tag_name,
        location_id=args.location_id,
        location_name=args.location_name,
        start_year=args.start_year,
        end_year=args.end_year,
        limit=args.limit,
    )
    print(json.dumps(pulse, indent=2) if args.json else markdown(pulse))


if __name__ == "__main__":
    main()
