#!/usr/bin/env python3
from __future__ import annotations

import argparse
import os
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any

import boto3
import httpx
from dotenv import load_dotenv


ROOT = Path(r"C:\Users\Paul\OneDrive\Documents\IterFact")
MCP_DIR = ROOT / "MCP"
ROOT_ENV = ROOT / ".env.supabase.local"
MCP_ENV = MCP_DIR / ".env"

if ROOT_ENV.exists():
    load_dotenv(ROOT_ENV)
if MCP_ENV.exists():
    load_dotenv(MCP_ENV, override=False)


def env(name: str, default: str = "") -> str:
    return os.getenv(name, default).strip()


def now_utc() -> datetime:
    return datetime.now(timezone.utc)


@dataclass
class Settings:
    supabase_url: str = env("NEXT_PUBLIC_SUPABASE_URL").rstrip("/")
    supabase_service_key: str = env("SUPABASE_SECRET_KEY") or env("SUPABASE_SERVICE_ROLE_KEY")
    cloudflare_api_token: str = env("CLOUDFLARE_API_TOKEN")
    cloudflare_zone_id: str = env("CLOUDFLARE_ZONE_ID")
    cloudflare_zone_name: str = env("CLOUDFLARE_ZONE_NAME") or "iterfact.com"
    r2_account_id: str = env("R2_ACCOUNT_ID")
    r2_access_key_id: str = env("R2_ACCESS_KEY_ID")
    r2_secret_access_key: str = env("R2_SECRET_ACCESS_KEY")
    r2_bucket: str = env("R2_BUCKET")
    r2_prefix: str = env("R2_PREFIX", "generated").strip("/")

    @property
    def has_supabase(self) -> bool:
        return bool(self.supabase_url and self.supabase_service_key)

    @property
    def has_cloudflare_zone(self) -> bool:
        return bool(self.cloudflare_api_token and self.cloudflare_zone_id)

    @property
    def has_r2(self) -> bool:
        return bool(
            self.r2_account_id
            and self.r2_access_key_id
            and self.r2_secret_access_key
            and self.r2_bucket
        )


SETTINGS = Settings()


class SupabaseRest:
    def __init__(self, url: str, service_key: str) -> None:
        self.base_url = f"{url}/rest/v1"
        self.headers = {
            "apikey": service_key,
            "Authorization": f"Bearer {service_key}",
            "Content-Type": "application/json",
        }

    def upsert(self, table: str, rows: list[dict[str, Any]], conflict_column: str) -> None:
        if not rows:
            return
        response = httpx.post(
            f"{self.base_url}/{table}",
            headers={**self.headers, "Prefer": "resolution=merge-duplicates,return=minimal"},
            params={"on_conflict": conflict_column},
            json=rows,
            timeout=120,
        )
        response.raise_for_status()


ZONE_HTTP_DAILY_QUERY = """
query ZoneHttpDaily($zoneTag: string!, $limit: Int!, $filter: ZoneHttpRequests1dGroupsFilter_InputObject) {
  viewer {
    zones(filter: { zoneTag: $zoneTag }) {
      httpRequests1dGroups(limit: $limit, filter: $filter) {
        dimensions { date }
        sum {
          requests
          bytes
          cachedRequests
          cachedBytes
          pageViews
        }
        uniq { uniques }
      }
    }
  }
}
"""


def fetch_cloudflare_zone_daily(days: int) -> list[dict[str, Any]]:
    if not SETTINGS.has_cloudflare_zone:
        return []

    end_date = now_utc().date()
    start_date = end_date - timedelta(days=max(1, days) - 1)
    response = httpx.post(
        "https://api.cloudflare.com/client/v4/graphql",
        headers={
            "Authorization": f"Bearer {SETTINGS.cloudflare_api_token}",
            "Content-Type": "application/json",
        },
        json={
            "query": ZONE_HTTP_DAILY_QUERY,
            "variables": {
                "zoneTag": SETTINGS.cloudflare_zone_id,
                "limit": max(1, min(days, 31)),
                "filter": {
                    "date_geq": start_date.isoformat(),
                    "date_leq": end_date.isoformat(),
                },
            },
        },
        timeout=120,
    )
    response.raise_for_status()
    payload = response.json()
    if payload.get("errors"):
        raise RuntimeError(f"Cloudflare GraphQL returned errors: {payload['errors']}")

    groups = (
        (((payload.get("data") or {}).get("viewer") or {}).get("zones") or [{}])[0].get("httpRequests1dGroups")
        or []
    )
    rows: list[dict[str, Any]] = []
    for group in groups:
        if not isinstance(group, dict):
            continue
        dims = group.get("dimensions") if isinstance(group.get("dimensions"), dict) else {}
        sums = group.get("sum") if isinstance(group.get("sum"), dict) else {}
        uniq = group.get("uniq") if isinstance(group.get("uniq"), dict) else {}
        bucket_date = str(dims.get("date") or "")[:10]
        if not bucket_date:
            continue
        rows.append(
            {
                "bucket_key": f"{SETTINGS.cloudflare_zone_id}:{bucket_date}",
                "bucket_date": bucket_date,
                "zone_id": SETTINGS.cloudflare_zone_id,
                "zone_name": SETTINGS.cloudflare_zone_name,
                "requests": int(sums.get("requests") or 0),
                "bytes": int(sums.get("bytes") or 0),
                "cached_requests": int(sums.get("cachedRequests") or 0),
                "cached_bytes": int(sums.get("cachedBytes") or 0),
                "page_views": int(sums.get("pageViews") or 0),
                "unique_visitors": int(uniq.get("uniques") or 0),
                "updated_at": now_utc().isoformat(),
            }
        )
    return rows


def snapshot_r2_inventory() -> list[dict[str, Any]]:
    if not SETTINGS.has_r2:
        return []

    client = boto3.client(
        "s3",
        endpoint_url=f"https://{SETTINGS.r2_account_id}.r2.cloudflarestorage.com",
        aws_access_key_id=SETTINGS.r2_access_key_id,
        aws_secret_access_key=SETTINGS.r2_secret_access_key,
        region_name="auto",
    )
    paginator = client.get_paginator("list_objects_v2")
    total_count = 0
    total_size = 0
    earliest: datetime | None = None
    latest: datetime | None = None

    for page in paginator.paginate(Bucket=SETTINGS.r2_bucket, Prefix=SETTINGS.r2_prefix):
        for item in page.get("Contents") or []:
            total_count += 1
            total_size += int(item.get("Size") or 0)
            last_modified = item.get("LastModified")
            if isinstance(last_modified, datetime):
                current = last_modified.astimezone(timezone.utc)
                earliest = current if earliest is None or current < earliest else earliest
                latest = current if latest is None or current > latest else latest

    bucket_date = now_utc().date().isoformat()
    return [
        {
            "bucket_key": f"{SETTINGS.r2_bucket}:{SETTINGS.r2_prefix}:{bucket_date}",
            "bucket_date": bucket_date,
            "bucket_name": SETTINGS.r2_bucket,
            "prefix": SETTINGS.r2_prefix,
            "object_count": total_count,
            "total_size_bytes": total_size,
            "earliest_object_at": earliest.isoformat() if earliest else None,
            "latest_object_at": latest.isoformat() if latest else None,
            "updated_at": now_utc().isoformat(),
        }
    ]


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Sync Cloudflare zone analytics and R2 inventory snapshots into Supabase.",
    )
    parser.add_argument("days", nargs="?", type=int, default=7, help="How many daily Cloudflare buckets to sync (default: 7).")
    return parser.parse_args()


def main() -> int:
    args = parse_args()
    if not SETTINGS.has_supabase:
        raise RuntimeError("Supabase service credentials are required to sync Cloudflare/R2 analytics.")

    supabase = SupabaseRest(SETTINGS.supabase_url, SETTINGS.supabase_service_key)

    zone_rows = fetch_cloudflare_zone_daily(args.days)
    if zone_rows:
        supabase.upsert("cloudflare_zone_daily", zone_rows, "bucket_key")

    r2_rows = snapshot_r2_inventory()
    if r2_rows:
        supabase.upsert("r2_inventory_daily", r2_rows, "bucket_key")

    print(
        {
            "cloudflare_zone_daily_rows": len(zone_rows),
            "r2_inventory_daily_rows": len(r2_rows),
        }
    )
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
