#!/usr/bin/env python3
"""Build an explainable competitive landscape from one Dealroom company."""

from __future__ import annotations

import argparse
import json
import os
import random
import time
from dataclasses import dataclass
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_COMPANY_ID = "a3741a91-cbe8-4a50-9dba-69b4ee612973"
DEFAULT_FILTER = (
    "and(hq_location[eq]:34,is_startup[eq]:true,"
    "company_status[eq]:operational)"
)
SCORING_TYPES = {
    "sector",
    "sub_industry",
    "industry",
    "client_focus",
    "technology",
    "income_stream",
}
RETRYABLE_STATUS = {429, 500, 502, 503, 504}


@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] | None = None) -> 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:
                return response.json()
            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)
            retry_after = response.headers.get("Retry-After")
            delay = min(float(retry_after), 3.0) if retry_after else 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 relevant_tags(items: list[dict[str, Any]] | None) -> list[dict[str, Any]]:
    return [
        {"id": tag.get("id"), "name": tag.get("name"), "type": tag.get("type")}
        for tag in (items or [])
        if tag.get("type") in SCORING_TYPES
    ]


def tag_key(tag: dict[str, Any]) -> tuple[str | None, Any]:
    return tag.get("type"), tag.get("id") or tag.get("name")


def normalize_company(
    company: dict[str, Any], rank: int, anchor_tags: list[dict[str, Any]]
) -> dict[str, Any]:
    tags = relevant_tags(company.get("tags"))
    anchor_keys = {tag_key(tag) for tag in anchor_tags}
    flags = company.get("company") or {}
    funding = company.get("funding_summary") or {}
    return {
        "rank": rank,
        "uuid": company.get("uuid"),
        "name": company.get("name"),
        "tagline": company.get("tagline"),
        "image": image_url(company.get("image")),
        "dealroom_url": company.get("dealroom_url"),
        "website": company.get("website"),
        "launch_year": company.get("launch_year"),
        "hq_city": company.get("hq_city"),
        "hq_country": company.get("hq_country"),
        "employee_count": company.get("employee_count"),
        "total_funding_usd": funding.get("total_funding", flags.get("total_funding")),
        "signal_rating": flags.get("signal_rating"),
        "is_hiring": bool(flags.get("is_hiring")),
        "open_jobs_count": flags.get("open_jobs_count") or 0,
        "shared_tags": [tag for tag in tags if tag_key(tag) in anchor_keys],
    }


def build_landscape(
    client: DealroomClient, company_id: str, limit: int, filter_value: str | None
) -> dict[str, Any]:
    anchor = client.get(f"/data/entities/{company_id}", {"currency": "USD"})["data"]
    anchor_tags = relevant_tags(anchor.get("tags"))
    params: dict[str, Any] = {
        "limit": limit,
        "include_total": "true",
        "currency": "USD",
    }
    if filter_value:
        params["filter"] = filter_value

    response = client.get(f"/data/companies/{company_id}/similar", params)
    companies = [
        normalize_company(item, rank, anchor_tags)
        for rank, item in enumerate(
            (item for item in rows(response) if item.get("uuid") != company_id),
            start=1,
        )
    ]
    return {
        "generated_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
        "source": "Dealroom API early access",
        "api_endpoint": "/data/companies/{company_id}/similar",
        "methodology": (
            "Dealroom supplies the ranking. Shared tags are intersected locally "
            "to make each result easier to inspect."
        ),
        "query": {
            "company_id": company_id,
            "filter": filter_value,
            "limit": limit,
            "include_total": True,
            "currency": "USD",
        },
        "anchor": {
            "uuid": anchor.get("uuid"),
            "name": anchor.get("name"),
            "tagline": anchor.get("tagline"),
            "image": image_url(anchor.get("image")),
            "dealroom_url": anchor.get("dealroom_url"),
            "website": anchor.get("website"),
            "hq_city": anchor.get("hq_city"),
            "hq_country": anchor.get("hq_country"),
            "tags": anchor_tags,
        },
        "summary": {
            "companies_returned": len(companies),
            "companies_hiring": sum(item["is_hiring"] for item in companies),
            "countries_represented": len(
                {item["hq_country"] for item in companies if item["hq_country"]}
            ),
        },
        "companies": companies,
    }


def compact_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:.1f}B"
    if number >= 1_000_000:
        return f"${number / 1_000_000:.1f}M"
    return f"${number:,.0f}"


def markdown(landscape: dict[str, Any]) -> str:
    anchor = landscape["anchor"]
    lines = [
        f"# Companies similar to {anchor['name']}",
        "",
        landscape["methodology"],
        "",
    ]
    for company in landscape["companies"]:
        location = ", ".join(
            value for value in [company["hq_city"], company["hq_country"]] if value
        ) or "Location unavailable"
        overlap = ", ".join(tag["name"] for tag in company["shared_tags"]) or "No shared tags returned"
        lines.extend(
            [
                f"## {company['rank']}. {company['name']}",
                company.get("tagline") or "No tagline available.",
                f"- Headquarters: {location}",
                f"- Total funding: {compact_money(company['total_funding_usd'])}",
                f"- Shared taxonomy: {overlap}",
                f"- Dealroom: {company.get('dealroom_url') or 'Not available'}",
                "",
            ]
        )
    return "\n".join(lines)


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Build an explainable Dealroom competitive landscape."
    )
    parser.add_argument("--company", default=DEFAULT_COMPANY_ID, help="Dealroom company UUID")
    parser.add_argument("--limit", type=int, default=12)
    parser.add_argument(
        "--filter",
        default=DEFAULT_FILTER,
        help="Optional Dealroom company filter DSL applied to the candidate pool",
    )
    parser.add_argument("--no-filter", action="store_true", help="Search the full candidate pool")
    parser.add_argument("--json", action="store_true", help="Print structured JSON")
    return parser.parse_args()


def main() -> None:
    load_dotenv()
    args = parse_args()
    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")
    if not 1 <= args.limit <= 500:
        raise SystemExit("--limit must be between 1 and 500")

    client = DealroomClient(
        client_id=client_id,
        client_secret=client_secret,
        user_agent=os.environ.get(
            "DEALROOM_USER_AGENT", "your-company-similar-companies/1.0"
        ),
    )
    landscape = build_landscape(
        client,
        company_id=args.company,
        limit=args.limit,
        filter_value=None if args.no_filter else args.filter,
    )
    print(json.dumps(landscape, indent=2) if args.json else markdown(landscape))


if __name__ == "__main__":
    main()
