import os
import csv
import string
import random
import requests
import xml.etree.ElementTree as ET
from datetime import datetime
from urllib.parse import urljoin
import subprocess
import sys
import time

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
SITES_FILE = os.path.join(BASE_DIR, "sites.txt")
KEY_DIR = os.path.join(BASE_DIR, "key")
CONFIG_FILE = os.path.join(BASE_DIR, "sites-config.csv")
REPORT_FILE = os.path.join(BASE_DIR, "report.csv")

INDEXNOW_HOST = "api.indexnow.org"
INDEXNOW_ENDPOINT = f"https://{INDEXNOW_HOST}/indexnow"


def generate_key(length=32):
    chars = string.ascii_letters + string.digits
    return "".join(random.choices(chars, k=length))


def ensure_dir(path):
    os.makedirs(path, exist_ok=True)


def read_sites(filepath):
    with open(filepath, "r", encoding="utf-8") as f:
        return [line.strip() for line in f if line.strip()]


def create_sites_config(sites):
    ensure_dir(KEY_DIR)
    rows = []
    for bucket in sites:
        key = generate_key()
        key_path = os.path.join(KEY_DIR, f"{key}.txt")
        with open(key_path, "w", encoding="utf-8") as f:
            f.write(key)

        site_url = f"https://storage.googleapis.com/{bucket}"
        sitemap_url = f"{site_url}/sitemap.xml"
        rows.append({"SiteUrl": site_url, "IndexNowKey": key, "Sitemaps": sitemap_url})

    with open(CONFIG_FILE, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=["SiteUrl", "IndexNowKey", "Sitemaps"])
        writer.writeheader()
        writer.writerows(rows)

    print(f"[OK] Sites config created: {CONFIG_FILE} ({len(rows)} sites)")
    print(f"[OK] Key files created in: {KEY_DIR}")
    return rows


def upload_key_to_bucket(bucket_name, key):
    key_file = os.path.join(KEY_DIR, f"{key}.txt")
    dest = f"gs://{bucket_name}/{key}.txt"
    key_url = f"https://storage.googleapis.com/{bucket_name}/{key}.txt"

    try:
        result = subprocess.run(
            f'gcloud storage cp "{key_file}" "{dest}"',
            capture_output=True, text=True, timeout=60, shell=True
        )
        if result.returncode != 0:
            print(f"  [FAIL] {result.stderr.strip()}")
            return False

        subprocess.run(
            f'gcloud storage objects update --public-access "{dest}"',
            capture_output=True, text=True, timeout=60, shell=True
        )

        verify = requests.get(key_url, timeout=15)
        if verify.status_code == 200 and verify.text.strip() == key:
            print(f"  [OK] Key uploaded & verified: {key_url}")
            return True
        else:
            print(f"  [FAIL] Key not accessible ({verify.status_code}): {key_url}")
            return False
    except subprocess.TimeoutExpired:
        print(f"  [TIMEOUT] Upload to {dest}")
        return False
    except requests.RequestException as e:
        print(f"  [FAIL] Verify key: {e}")
        return False


def fetch_sitemap_urls(sitemap_url):
    urls = []
    try:
        resp = requests.get(sitemap_url, timeout=30)
        if resp.status_code != 200:
            print(f"  [WARN] Sitemap not found ({resp.status_code}): {sitemap_url}")
            return urls

        root = ET.fromstring(resp.content)
        ns = {"sm": "http://www.sitemaps.org/schemas/sitemap/0.9"}

        for url_elem in root.findall(".//sm:url/sm:loc", ns):
            if url_elem.text:
                urls.append(url_elem.text.strip())

        if not urls:
            for url_elem in root.findall(".//url/loc"):
                if url_elem.text:
                    urls.append(url_elem.text.strip())

        print(f"  [OK] Found {len(urls)} URLs in {sitemap_url}")
    except requests.RequestException as e:
        print(f"  [FAIL] Fetch sitemap {sitemap_url}: {e}")
    except ET.ParseError as e:
        print(f"  [FAIL] Parse sitemap {sitemap_url}: {e}")

    return urls


def submit_to_indexnow(host, key, urls, key_location=None):
    if not urls:
        return {"status": 0, "error": "No URLs to submit"}

    payload = {
        "host": host,
        "key": key,
        "urlList": urls
    }
    if key_location:
        payload["keyLocation"] = key_location

    try:
        resp = requests.post(
            INDEXNOW_ENDPOINT,
            json=payload,
            headers={"Content-Type": "application/json; charset=utf-8"},
            timeout=60
        )
        return {"status": resp.status_code, "error": None if resp.status_code == 200 else resp.text}
    except requests.RequestException as e:
        return {"status": 0, "error": str(e)}


def create_report(results):
    fieldnames = ["SiteUrl", "Bucket", "IndexNowKey", "SitemapUrl", "UrlsFound",
                  "UrlsSubmitted", "StatusCode", "Error", "Timestamp"]

    with open(REPORT_FILE, "w", newline="", encoding="utf-8") as f:
        writer = csv.DictWriter(f, fieldnames=fieldnames)
        writer.writeheader()
        writer.writerows(results)

    print(f"\n[OK] Report saved: {REPORT_FILE}")


def main():
    print("=" * 60)
    print("  IndexNow Submitter for GCS Static Sites")
    print("=" * 60)

    sites = read_sites(SITES_FILE)
    print(f"\n[INFO] Loaded {len(sites)} buckets from {SITES_FILE}")

    config_rows = create_sites_config(sites)

    print(f"\n{'='*60}")
    print("  Processing buckets (upload + submit)...")
    print(f"{'='*60}")

    results = []
    total_urls = 0
    upload_success = 0
    upload_fail = 0

    for row in config_rows:
        bucket = row["SiteUrl"].split("/")[-1]
        print(f"\n--- Processing: {bucket} ---")

        uploaded = upload_key_to_bucket(bucket, row["IndexNowKey"])
        if uploaded:
            upload_success += 1
        else:
            upload_fail += 1
            print("  [SKIP] Skipping submit due to upload failure")
            results.append({
                "SiteUrl": row["SiteUrl"],
                "Bucket": bucket,
                "IndexNowKey": row["IndexNowKey"],
                "SitemapUrl": row["Sitemaps"],
                "UrlsFound": 0,
                "UrlsSubmitted": 0,
                "StatusCode": 0,
                "Error": "Key upload failed",
                "Timestamp": datetime.now().isoformat()
            })
            continue

        urls = fetch_sitemap_urls(row["Sitemaps"])
        host = "storage.googleapis.com"
        key_location = f"https://storage.googleapis.com/{bucket}/{row['IndexNowKey']}.txt"

        print(f"  Submitting {len(urls)} URLs to IndexNow...")
        submit_result = {"status": 0, "error": "Not started"}
        max_retries = 10
        attempt = 0

        while attempt < max_retries:
            attempt += 1
            submit_result = submit_to_indexnow(host, row["IndexNowKey"], urls, key_location)

            if submit_result["status"] == 200:
                break

            if submit_result["status"] == 403:
                print(f"  [RETRY {attempt}/{max_retries}] Got 403, retrying in 5s...")
                time.sleep(5)
            else:
                break

        result = {
            "SiteUrl": row["SiteUrl"],
            "Bucket": bucket,
            "IndexNowKey": row["IndexNowKey"],
            "SitemapUrl": row["Sitemaps"],
            "UrlsFound": len(urls),
            "UrlsSubmitted": len(urls),
            "StatusCode": submit_result["status"],
            "Error": submit_result["error"] or "",
            "Timestamp": datetime.now().isoformat()
        }
        results.append(result)
        total_urls += len(urls)

        status_msg = "OK" if submit_result["status"] == 200 else f"FAIL ({submit_result['status']})"
        print(f"  Result: {status_msg}")

        time.sleep(0.5)

    create_report(results)

    print(f"\n{'='*60}")
    print("  SUMMARY")
    print(f"{'='*60}")
    print(f"  Total buckets processed: {len(results)}")
    print(f"  Total URLs submitted: {total_urls}")

    success_count = sum(1 for r in results if r["StatusCode"] == 200)
    fail_count = len(results) - success_count
    print(f"  Successful submissions: {success_count}")
    print(f"  Failed submissions: {fail_count}")
    print(f"  Report: {REPORT_FILE}")
    print(f"{'='*60}")


if __name__ == "__main__":
    main()
