"""
Genesis Clone Pipeline — Netlify Deployment Module
===================================================
Deploys cloned HTML to Netlify via API (automated, no UI needed).
Falls back to local file server if no Netlify token is available.

Netlify API docs: https://docs.netlify.com/api/get-started/
"""

import os
import sys
import io
import json
import time
import logging
import zipfile
import hashlib
from pathlib import Path
from typing import Optional

import requests

logger = logging.getLogger("clone_pipeline.deployer")

# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------

NETLIFY_API_BASE = "https://api.netlify.com/api/v1"
LOCAL_DEPLOY_DIR = Path("/mnt/e/genesis-system/scripts/clone_pipeline/output")


# ---------------------------------------------------------------------------
# Token resolution
# ---------------------------------------------------------------------------

def _get_netlify_token() -> Optional[str]:
    """Resolve Netlify personal access token from environment."""
    for key in ("NETLIFY_TOKEN", "NETLIFY_ACCESS_TOKEN", "NETLIFY_AUTH_TOKEN"):
        val = os.environ.get(key)
        if val:
            return val

    # Try genesis secrets loader
    try:
        sys.path.insert(0, "/mnt/e/genesis-system/core")
        from secrets_loader import _get_env
        token = _get_env("NETLIFY_TOKEN") or _get_env("NETLIFY_ACCESS_TOKEN")
        if token:
            return token
    except Exception:
        pass

    return None


# ---------------------------------------------------------------------------
# Netlify deployment helpers
# ---------------------------------------------------------------------------

def _create_zip(html_content: str, extra_files: Optional[dict] = None) -> bytes:
    """
    Create an in-memory ZIP file for Netlify deployment.

    Args:
        html_content: The main index.html content
        extra_files: Optional dict of {filename: content} for additional files

    Returns:
        Bytes of the ZIP archive
    """
    zip_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_buffer, "w", zipfile.ZIP_DEFLATED) as zf:
        zf.writestr("index.html", html_content.encode("utf-8"))

        # Netlify redirects file (single-page app support)
        zf.writestr("_redirects", "/* /index.html 200\n")

        # robots.txt
        zf.writestr("robots.txt", "User-agent: *\nAllow: /\n")

        if extra_files:
            for filename, content in extra_files.items():
                if isinstance(content, str):
                    zf.writestr(filename, content.encode("utf-8"))
                else:
                    zf.writestr(filename, content)

    return zip_buffer.getvalue()


def _create_netlify_site(token: str, site_name: str) -> Optional[dict]:
    """
    Create a new Netlify site and return site info.

    Returns dict with 'id' and 'url' keys, or None on failure.
    """
    # Sanitise site name for Netlify (lowercase, alphanumeric + hyphens only)
    clean_name = "".join(
        c if c.isalnum() or c == "-" else "-"
        for c in site_name.lower().replace(" ", "-")
    )
    # Remove consecutive hyphens and leading/trailing hyphens
    import re
    clean_name = re.sub(r"-{2,}", "-", clean_name).strip("-")
    # Netlify site names must be 3-63 chars
    if len(clean_name) < 3:
        clean_name = f"genesis-{clean_name}"
    if len(clean_name) > 63:
        clean_name = clean_name[:63].rstrip("-")

    # Add a short hash suffix to avoid name collisions
    suffix = hashlib.md5(site_name.encode()).hexdigest()[:6]
    final_name = f"{clean_name}-{suffix}"[:63]

    logger.info(f"[Deployer] Creating Netlify site: {final_name}")

    try:
        resp = requests.post(
            f"{NETLIFY_API_BASE}/sites",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/json",
            },
            json={"name": final_name},
            timeout=30,
        )
        resp.raise_for_status()
        data = resp.json()
        return {
            "id": data["id"],
            "url": data.get("ssl_url") or data.get("url"),
            "name": data.get("name"),
            "admin_url": data.get("admin_url"),
        }
    except requests.exceptions.HTTPError as e:
        if e.response.status_code == 422:
            # Name already taken — try with different suffix
            alt_name = f"genesis-tradie-{suffix}"
            logger.info(f"[Deployer] Name taken, trying: {alt_name}")
            try:
                resp2 = requests.post(
                    f"{NETLIFY_API_BASE}/sites",
                    headers={
                        "Authorization": f"Bearer {token}",
                        "Content-Type": "application/json",
                    },
                    json={"name": alt_name},
                    timeout=30,
                )
                resp2.raise_for_status()
                data2 = resp2.json()
                return {
                    "id": data2["id"],
                    "url": data2.get("ssl_url") or data2.get("url"),
                    "name": data2.get("name"),
                    "admin_url": data2.get("admin_url"),
                }
            except Exception as e2:
                logger.error(f"[Deployer] Fallback site creation failed: {e2}")
                return None
        logger.error(f"[Deployer] Site creation failed: {e}")
        return None
    except Exception as e:
        logger.error(f"[Deployer] Unexpected error creating site: {e}")
        return None


def _deploy_zip_to_site(token: str, site_id: str, zip_bytes: bytes) -> Optional[str]:
    """
    Deploy a ZIP file to an existing Netlify site.

    Returns the live URL on success, None on failure.
    """
    logger.info(f"[Deployer] Deploying ZIP ({len(zip_bytes)} bytes) to site {site_id}...")

    try:
        resp = requests.post(
            f"{NETLIFY_API_BASE}/sites/{site_id}/deploys",
            headers={
                "Authorization": f"Bearer {token}",
                "Content-Type": "application/zip",
            },
            data=zip_bytes,
            timeout=120,
        )
        resp.raise_for_status()
        data = resp.json()

        deploy_id = data.get("id")
        state = data.get("state", "unknown")
        logger.info(f"[Deployer] Deploy created: {deploy_id}, state: {state}")

        # Wait for deploy to become ready (poll up to 60 seconds)
        site_url = data.get("ssl_url") or data.get("deploy_ssl_url")

        if state not in ("ready", "current"):
            site_url = _wait_for_deploy(token, site_id, deploy_id, timeout=60)

        if site_url:
            logger.info(f"[Deployer] Site live at: {site_url}")
        return site_url

    except requests.exceptions.HTTPError as e:
        logger.error(f"[Deployer] Deploy failed HTTP {e.response.status_code}: {e.response.text[:500]}")
        return None
    except Exception as e:
        logger.error(f"[Deployer] Deploy failed: {e}")
        return None


def _wait_for_deploy(
    token: str, site_id: str, deploy_id: str, timeout: int = 60
) -> Optional[str]:
    """Poll Netlify until deploy is ready or timeout."""
    start = time.time()
    while time.time() - start < timeout:
        try:
            resp = requests.get(
                f"{NETLIFY_API_BASE}/sites/{site_id}/deploys/{deploy_id}",
                headers={"Authorization": f"Bearer {token}"},
                timeout=15,
            )
            resp.raise_for_status()
            data = resp.json()
            state = data.get("state", "")
            if state in ("ready", "current"):
                return data.get("ssl_url") or data.get("deploy_ssl_url")
            elif state == "error":
                logger.error(f"[Deployer] Deploy errored: {data.get('error_message', 'unknown')}")
                return None
            logger.info(f"[Deployer] Deploy state: {state}, waiting...")
            time.sleep(5)
        except Exception as e:
            logger.warning(f"[Deployer] Poll error: {e}")
            time.sleep(5)

    logger.warning("[Deployer] Timed out waiting for deploy")
    return None


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def deploy_to_netlify(
    html_content: str,
    site_name: str,
    extra_files: Optional[dict] = None,
) -> Optional[str]:
    """
    Deploy HTML to Netlify via API.

    Args:
        html_content: Complete HTML string to deploy as index.html
        site_name: Name for the Netlify site (will be slugified)
        extra_files: Optional additional files dict {filename: content}

    Returns:
        Live HTTPS URL (e.g. https://bob-s-plumbing-a1b2c3.netlify.app),
        or None if deployment fails.
    """
    token = _get_netlify_token()
    if not token:
        logger.warning("[Deployer] No NETLIFY_TOKEN found. Skipping Netlify deployment.")
        return None

    # Create site
    site_info = _create_netlify_site(token, site_name)
    if not site_info:
        logger.error("[Deployer] Failed to create Netlify site")
        return None

    logger.info(f"[Deployer] Site created: {site_info['name']} ({site_info['id']})")

    # Create ZIP and deploy
    zip_bytes = _create_zip(html_content, extra_files)
    live_url = _deploy_zip_to_site(token, site_info["id"], zip_bytes)

    if live_url:
        logger.info(f"[Deployer] Deployment successful: {live_url}")
        return live_url
    else:
        # Return the expected URL even if poll timed out
        # Netlify typically deploys within 30s
        expected_url = site_info.get("url") or f"https://{site_info['name']}.netlify.app"
        logger.info(f"[Deployer] Deploy initiated, expected URL: {expected_url}")
        return expected_url


def save_locally(
    html_content: str,
    output_dir: Path,
    filename: str = "index.html",
) -> Path:
    """
    Save HTML to local file as fallback when no Netlify token is available.

    Returns the path to the saved file.
    """
    output_dir.mkdir(parents=True, exist_ok=True)
    out_path = output_dir / filename
    out_path.write_text(html_content, encoding="utf-8")
    logger.info(f"[Deployer] HTML saved locally: {out_path}")
    return out_path


def start_local_server(output_dir: Path, port: int = 8787) -> str:
    """
    Start a simple HTTP server to preview the cloned site locally.

    Returns the URL.
    """
    import threading
    import http.server
    import functools

    handler = functools.partial(
        http.server.SimpleHTTPRequestHandler,
        directory=str(output_dir)
    )

    def run_server():
        with http.server.HTTPServer(("0.0.0.0", port), handler) as httpd:
            logger.info(f"[Deployer] Local server running at http://localhost:{port}")
            httpd.serve_forever()

    thread = threading.Thread(target=run_server, daemon=True)
    thread.start()

    return f"http://localhost:{port}"


# ---------------------------------------------------------------------------
# CLI test
# ---------------------------------------------------------------------------

if __name__ == "__main__":
    logging.basicConfig(level=logging.INFO, format="%(levelname)s %(message)s")

    test_html = "<html><body><h1>Test Deployment</h1><p>Genesis Clone Pipeline test.</p></body></html>"

    token = _get_netlify_token()
    if token:
        print(f"Netlify token found, attempting deployment...")
        url = deploy_to_netlify(test_html, "genesis-test-deploy")
        if url:
            print(f"Deployed: {url}")
        else:
            print("Deployment failed")
    else:
        print("No NETLIFY_TOKEN set, saving locally...")
        path = save_locally(test_html, LOCAL_DEPLOY_DIR / "test", "test.html")
        print(f"Saved: {path}")
