#!/usr/bin/env python3


"""
FPRates Static helper - v0.3

What it does:
  1. Asks for your EDF login.
  2. Fetches your electricity tariff history from EDF.
  3. Shows you exactly what would be sent to FPRates.
  4. Sends it only if you say yes, using your FPRates upload token.

Your EDF login is only ever sent to EDF (api.edfgb-kraken.energy).
Only the details shown in step 3 are sent to FPRates (members.fprates.online).
Nothing is saved to disk. Standard library only - nothing to install.
"""

import getpass
import json
import sys
import urllib.error
import urllib.request
from datetime import datetime, timedelta

EDF_URL = "https://api.edfgb-kraken.energy/v1/graphql/"
MEMBERS_URL = "https://members.fprates.online/api/agreements"
USER_AGENT = "fprates-static/0.3"

LOGIN = """
mutation ($email: String!, $password: String!) {
  obtainKrakenToken(input: {email: $email, password: $password}) { token }
}"""

ACCOUNTS = "query { viewer { accounts { number } } }"

AGREEMENTS = """
query ($acc: String!) {
  account(accountNumber: $acc) {
    properties {
      electricityMeterPoints {
        agreements(includeInactive: true) {
          validFrom
          validTo
          tariff {
            ... on StandardTariff   { productCode tariffCode displayName }
            ... on HalfHourlyTariff { productCode tariffCode displayName }
            ... on DayNightTariff   { productCode tariffCode displayName }
            ... on ThreeRateTariff  { productCode tariffCode displayName }
            ... on PrepayTariff     { productCode tariffCode displayName }
          }
        }
      }
    }
  }
}"""


def edf(query, variables=None, token=None):
    """POST a GraphQL query to EDF and return its 'data', or exit with a clear message."""
    body = json.dumps({"query": query, "variables": variables or {}}).encode()
    req = urllib.request.Request(EDF_URL, data=body, method="POST")
    req.add_header("Content-Type", "application/json")
    req.add_header("User-Agent", USER_AGENT)
    if token:
        req.add_header("Authorization", token)
    try:
        with urllib.request.urlopen(req, timeout=15) as r:
            data = json.load(r)
    except urllib.error.HTTPError as e:
        sys.exit(f"EDF returned HTTP {e.code}.")
    except urllib.error.URLError as e:
        sys.exit(f"Could not reach EDF: {e.reason}")
    if data.get("errors"):
        sys.exit("EDF said: " + "; ".join(err.get("message", "?") for err in data["errors"]))
    return data["data"]


def uk_date(ts):
    """EDF tariff boundaries are UK midnight: 23:00 UTC in summer, 00:00 UTC in winter."""
    if not ts:
        return "open"
    return (datetime.fromisoformat(ts) + timedelta(hours=1)).strftime("%d %b %Y")


def uk_end_date(ts):
    """The end boundary is the next tariff's start, so the last day is the day before."""
    if not ts:
        return "open"
    return (datetime.fromisoformat(ts) + timedelta(hours=1)
            - timedelta(days=1)).strftime("%d %b %Y")


def upload(payload, token):
    """Send the stripped-down payload to FPRates."""
    body = json.dumps(payload).encode()
    req = urllib.request.Request(MEMBERS_URL, data=body, method="POST")
    req.add_header("Content-Type", "application/json")
    req.add_header("User-Agent", USER_AGENT)
    req.add_header("X-Upload-Token", token)
    try:
        with urllib.request.urlopen(req, timeout=15) as r:
            result = json.load(r)
    except urllib.error.HTTPError as e:
        try:
            msg = json.load(e).get("error", "")
        except Exception:
            msg = ""
        sys.exit(f"FPRates rejected the upload (HTTP {e.code}): {msg}")
    except urllib.error.URLError as e:
        sys.exit(f"Could not reach FPRates: {e.reason}")
    print(f"\nDone - {result.get('stored')} tariff records stored.")


def main():
    print(__doc__)
    email = input("EDF email: ").strip()
    password = getpass.getpass("EDF password (right-click to paste - nothing will show): ")

    try:
        token = edf(LOGIN, {"email": email, "password": password})["obtainKrakenToken"]["token"]
    except SystemExit as e:
        sys.exit(f"EDF login failed ({e}). Check your email and password and try again.")
    del password

    accounts = [a["number"] for a in edf(ACCOUNTS, token=token)["viewer"]["accounts"]]

    agreements = []
    for acc in accounts:
        data = edf(AGREEMENTS, {"acc": acc}, token)
        for prop in data["account"]["properties"]:
            for mp in prop["electricityMeterPoints"]:
                for ag in mp["agreements"]:
                    t = ag.get("tariff") or {}
                    if not t.get("tariffCode"):
                        continue
                    agreements.append({
                        "name": t.get("displayName", ""),
                        "productCode": t["productCode"],
                        "tariffCode": t["tariffCode"],
                        "validFrom": ag["validFrom"],
                        "validTo": ag["validTo"],
                    })

    if not agreements:
        sys.exit("No electricity agreements found on this login.")

    agreements.sort(key=lambda a: a["validFrom"])

    print("\nYour EDF electricity tariffs:\n")
    for a in agreements:
        print(f"  {uk_date(a['validFrom'])} to {uk_end_date(a['validTo'])}  {a['name']}")

    # Only these four fields ever go to FPRates. The display name above stays on your PC.
    payload = [
        {k: a[k] for k in ("productCode", "tariffCode", "validFrom", "validTo")}
        for a in agreements
    ]
    print("\nThis is everything that would be sent to FPRates:\n")
    print(json.dumps(payload, indent=2))

    if input("\nSend this to FPRates? (y/n): ").strip().lower() != "y":
        sys.exit("Nothing sent.")
    token = getpass.getpass(
        "FPRates upload token (right-click to paste - nothing will show): ").strip()
    upload(payload, token)


if __name__ == "__main__":
    main()
