#!/usr/bin/env python3
"""
Redash Dashboard Builder — librería reutilizable para crear dashboards via API.

Uso desde scripts generados:

    import sys, os
    sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
    from redash_builder import RedashBuilder

    b = RedashBuilder(config_path="config.json")
    qid = b.create_query("Mi query", "SELECT 1", data_source_id=1)
    b.execute_query(qid)
    counter = b.create_counter(qid, "Total", "col_name", "Label")
    dash_id, slug = b.create_dashboard("Mi dashboard")
    b.add_widget(dash_id, counter, row=0, col=0)
    b.publish(dash_id)
    print(b.dashboard_url(dash_id, slug))

Requiere: pip install requests
"""

import json
import os
import sys
import time

try:
    import requests
except ImportError:
    print("Error: 'requests' no está instalada. Instalar con: pip install requests", file=sys.stderr)
    sys.exit(1)


class RedashBuilder:
    def __init__(self, config_path=None, redash_url=None, api_key=None):
        if redash_url and api_key:
            self.url = redash_url.rstrip("/")
            self.key = api_key
        elif config_path:
            try:
                with open(config_path) as f:
                    cfg = json.load(f)
            except FileNotFoundError:
                print(f"Error: config.json not found at {config_path}", file=sys.stderr)
                sys.exit(1)
            except json.JSONDecodeError as e:
                print(f"Error: config.json contains invalid JSON: {e}", file=sys.stderr)
                sys.exit(1)
            self.url = cfg["redash_url"].rstrip("/")
            self.key = cfg["api_key"]
        else:
            raise ValueError("Provide config_path or redash_url+api_key")
        self.headers = {
            "Authorization": f"Key {self.key}",
            "Content-Type": "application/json",
        }

    def api(self, method, path, **kwargs):
        r = requests.request(
            method, f"{self.url}/api/{path}",
            headers=self.headers, timeout=120, **kwargs,
        )
        if not r.ok:
            print(f"HTTP {r.status_code} {method} {path}: {r.text}", file=sys.stderr)
            r.raise_for_status()
        return r.json() if r.text else {}

    # ------------------------------------------------------------------
    # Queries
    # ------------------------------------------------------------------

    def create_query(self, name, sql, data_source_id=1, description="",
                     params=None, since_default=None):
        options = {}
        if params:
            options["parameters"] = params
        elif since_default:
            options["parameters"] = [{
                "name": "since", "title": "Desde",
                "type": "date", "value": since_default, "global": True,
            }]
        body = {
            "name": name, "description": description,
            "query": sql, "data_source_id": data_source_id,
            "schedule": None, "options": options, "is_draft": False,
        }
        res = self.api("POST", "queries", json=body)
        qid = res["id"]
        self.api("POST", f"queries/{qid}", json={"is_draft": False})
        default_table_viz = None
        for v in res.get("visualizations", []):
            if v.get("type") == "TABLE":
                default_table_viz = v["id"]
        return qid, default_table_viz

    def execute_query(self, query_id, params=None, max_wait=240):
        body = {"max_age": 0}
        if params:
            body["parameters"] = params
        res = self.api("POST", f"queries/{query_id}/results", json=body)
        if "job" not in res:
            return res.get("query_result", {}).get("id")
        job_id = res["job"]["id"]
        deadline = time.time() + max_wait
        while time.time() < deadline:
            j = self.api("GET", f"jobs/{job_id}").get("job", {})
            if j.get("status") == 3:
                return j.get("query_result_id")
            if j.get("status") == 4:
                print(f"Query {query_id} falló: {j.get('error')}", file=sys.stderr)
                return None
            time.sleep(2)
        print(f"Timeout esperando query {query_id}", file=sys.stderr)
        return None

    # ------------------------------------------------------------------
    # Visualizaciones
    # ------------------------------------------------------------------

    def create_counter(self, query_id, name, column, label, fmt="0,0"):
        body = {
            "query_id": query_id, "name": name, "type": "COUNTER",
            "options": {
                "counterLabel": label, "counterColName": column,
                "rowNumber": 1, "targetRowNumber": 1,
                "stringDecimal": 0, "stringDecChar": ",",
                "stringThouSep": ".", "tooltipFormat": fmt,
            },
            "description": "",
        }
        return self.api("POST", "visualizations", json=body)["id"]

    def create_pie(self, query_id, name, label_col, value_col):
        body = {
            "query_id": query_id, "name": name, "type": "CHART",
            "options": {
                "globalSeriesType": "pie", "sortX": True,
                "legend": {"enabled": True, "placement": "auto"},
                "yAxis": [{"type": "linear"}, {"type": "linear", "opposite": True}],
                "xAxis": {"type": "-", "labels": {"enabled": True}},
                "series": {"stacking": None},
                "columnMapping": {label_col: "x", value_col: "y"},
                "direction": {"type": "counterclockwise"},
                "numberFormat": "0,0", "percentFormat": "0.00%",
                "missingValuesAsZero": True, "showDataLabels": True,
            },
            "description": "",
        }
        return self.api("POST", "visualizations", json=body)["id"]

    def create_bar_chart(self, query_id, name, x_col, y_col, x_type="category"):
        body = {
            "query_id": query_id, "name": name, "type": "CHART",
            "options": {
                "globalSeriesType": "column", "sortX": True,
                "legend": {"enabled": True, "placement": "auto"},
                "xAxis": {"type": x_type, "labels": {"enabled": True}},
                "yAxis": [{"type": "linear"}],
                "series": {"stacking": None},
                "columnMapping": {x_col: "x", y_col: "y"},
                "showDataLabels": True, "missingValuesAsZero": True,
                "numberFormat": "0,0",
            },
            "description": "",
        }
        return self.api("POST", "visualizations", json=body)["id"]

    def create_line_chart(self, query_id, name, x_col, y_col, x_type="datetime"):
        body = {
            "query_id": query_id, "name": name, "type": "CHART",
            "options": {
                "globalSeriesType": "line", "sortX": True,
                "legend": {"enabled": True, "placement": "auto"},
                "xAxis": {"type": x_type, "labels": {"enabled": True}},
                "yAxis": [{"type": "linear"}],
                "series": {"stacking": None},
                "columnMapping": {x_col: "x", y_col: "y"},
                "showDataLabels": False, "missingValuesAsZero": True,
                "numberFormat": "0,0",
            },
            "description": "",
        }
        return self.api("POST", "visualizations", json=body)["id"]

    def create_combo_chart(self, query_id, name, x_col,
                           bar_col, line_col,
                           bar_label=None, line_label=None,
                           bar_y_title="", line_y_title=""):
        body = {
            "query_id": query_id, "name": name, "type": "CHART",
            "options": {
                "globalSeriesType": "column", "sortX": True,
                "legend": {"enabled": True, "placement": "auto"},
                "xAxis": {"type": "datetime", "labels": {"enabled": True}},
                "yAxis": [
                    {"type": "linear", "title": {"text": bar_y_title}},
                    {"type": "linear", "opposite": True, "title": {"text": line_y_title}},
                ],
                "series": {"stacking": None},
                "seriesOptions": {
                    bar_col: {"type": "column", "yAxis": 0, "name": bar_label or bar_col},
                    line_col: {"type": "line", "yAxis": 1, "name": line_label or line_col},
                },
                "columnMapping": {x_col: "x", bar_col: "y", line_col: "y"},
                "showDataLabels": False, "missingValuesAsZero": True,
                "numberFormat": "0,0",
            },
            "description": "",
        }
        return self.api("POST", "visualizations", json=body)["id"]

    # ------------------------------------------------------------------
    # Dashboard
    # ------------------------------------------------------------------

    def create_dashboard(self, name):
        res = self.api("POST", "dashboards", json={"name": name})
        return res["id"], res.get("slug")

    def add_widget(self, dashboard_id, viz_id, row, col,
                   size_x=3, size_y=8, param_mappings=None):
        body = {
            "dashboard_id": dashboard_id,
            "visualization_id": viz_id, "text": "",
            "options": {
                "position": {
                    "col": col, "row": row,
                    "sizeX": size_x, "sizeY": size_y,
                    "minSizeX": 1, "maxSizeX": 6,
                    "minSizeY": 1, "maxSizeY": 1000,
                },
            },
            "width": 1,
        }
        if param_mappings:
            body["options"]["parameterMappings"] = param_mappings
        return self.api("POST", "widgets", json=body)

    def add_text_widget(self, dashboard_id, text, row, col,
                        size_x=6, size_y=2):
        body = {
            "dashboard_id": dashboard_id,
            "visualization_id": None, "text": text,
            "options": {
                "position": {
                    "col": col, "row": row,
                    "sizeX": size_x, "sizeY": size_y,
                    "minSizeX": 1, "maxSizeX": 6,
                    "minSizeY": 1, "maxSizeY": 1000,
                },
            },
            "width": 1,
        }
        return self.api("POST", "widgets", json=body)

    def publish(self, dashboard_id):
        self.api("POST", f"dashboards/{dashboard_id}", json={"is_draft": False})

    def dashboard_url(self, dashboard_id, slug=None):
        if slug:
            return f"{self.url}/dashboards/{dashboard_id}-{slug}"
        return f"{self.url}/dashboards/{dashboard_id}"

    # ------------------------------------------------------------------
    # Helpers
    # ------------------------------------------------------------------

    @staticmethod
    def since_param_mapping(since_value):
        return {
            "since": {
                "name": "since", "type": "dashboard-level",
                "mapTo": "since", "value": since_value, "title": "Desde",
            }
        }
