import base64
import json
import logging
import os
import threading
import time
import webbrowser
import xml.etree.ElementTree as ET
from datetime import datetime
from urllib.parse import urlencode

import click
import keyring
import requests
from authlib.integrations.requests_client import OAuth2Session
from dateutil import parser as date_parser
from flask import Flask, request

from eocube.integrations.auth.client import ROCS_DISCOVERY_URL, StorageCredentials

EOCUBE_CLI_CLIENT_ID = "eocube-cli"
EOCUBE_CLI_REDIRECT_URI = "http://localhost:5123/callback"

login_done = threading.Event()

authorize_url = None
token_url = None


def get_token():
    access_token = keyring.get_password("eocube-cli", "access-token")
    refresh_token = keyring.get_password("eocube-cli", "offline-refresh-token")

    if access_token and _is_token_valid(access_token):
        print("✅ Found valid access token in keyring")
        return access_token

    if not refresh_token:
        raise click.ClickException(
            "❌ No refresh token. Please login: `eocube auth login`"
        )

    # Facem refresh cu authlib
    metadata = requests.get(ROCS_DISCOVERY_URL).json()
    token_url = metadata["token_endpoint"]

    print("🔄 Token expired or missing. Trying to refresh ...")

    session = OAuth2Session(
        client_id=EOCUBE_CLI_CLIENT_ID, client_secret=None  # public client
    )

    token = session.refresh_token(token_url, refresh_token=refresh_token)

    new_access_token = token["access_token"]
    new_refresh_token = token.get("refresh_token", refresh_token)

    keyring.set_password("eocube-cli", "access-token", new_access_token)
    keyring.set_password("eocube-cli", "offline-refresh-token", new_refresh_token)

    print("✅ Saved new tokens in keyring.")

    return new_access_token


def _is_token_valid(token):
    try:
        payload_part = token.split(".")[1]
        # Padding base64 dacă lipsește
        padded = payload_part + "=" * (-len(payload_part) % 4)
        payload = json.loads(base64.urlsafe_b64decode(padded.encode()))
        exp = payload.get("exp")
        return exp and time.time() < exp - 30
    except Exception as e:
        print("⚠️ Error validating token:", e)
        return False


@click.group("auth")
def auth_cli():
    """Authentication related functionality"""


@auth_cli.command("info")
def info_authentication():
    """Afișează informații despre utilizatorul autentificat"""

    token = get_token()
    try:
        payload_part = token.split(".")[1]
        padded = payload_part + "=" * (-len(payload_part) % 4)
        decoded = json.loads(base64.urlsafe_b64decode(padded.encode()))

        print("🧾 Token Information:\n")

        for key in [
            "sub",
            "preferred_username",
            "email",
            "name",
            "given_name",
            "family_name",
            "roles",
            "scope",
            "policy",
        ]:
            if key in decoded:
                print(f"{key:>20}: {decoded[key]}")
        # ✅ Expirare
        if "exp" in decoded:
            exp_time = datetime.fromtimestamp(decoded["exp"])
            print(f"{'exp':>20}: {decoded['exp']} (expires on {exp_time})")

        # 🔐 Roluri, dacă există
        if "realm_access" in decoded and "roles" in decoded["realm_access"]:
            roles = decoded["realm_access"]["roles"]
            print(f"{'roles':>20}: {', '.join(roles)}")
    except Exception as e:
        print("⚠️ Could not decode payload", e)


@auth_cli.command("login")
def login():
    """Login to EOCube.ro"""

    logging.getLogger("werkzeug").setLevel(logging.ERROR)

    app = Flask(__name__)
    server_thread = None

    def start_oauth_flow():
        global authorize_url
        global token_url
        oauth = OAuth2Session(
            EOCUBE_CLI_CLIENT_ID, redirect_uri=EOCUBE_CLI_REDIRECT_URI
        )
        metadata = requests.get(ROCS_DISCOVERY_URL).json()
        authorize_url = metadata["authorization_endpoint"]
        token_url = metadata["token_endpoint"]
        uri, state = oauth.create_authorization_url(
            authorize_url, scope="openid offline_access eocube-object-storage"
        )
        webbrowser.open(uri)
        return oauth

    def run_flask():
        app.run(port=5123, use_reloader=False)

    def shutdown_server():
        func = request.environ.get("werkzeug.server.shutdown")
        if func:
            func()

    @app.route("/callback")
    def callback():
        global oauth_token
        global token_url

        code = request.args.get("code")
        if not code:
            return "❌ No code received. Something went wrong."

        token = app.oauth_session.fetch_token(token_url, code=code)

        oauth_token = token  # salvează tokenul pt codul principal
        login_done.set()
        shutdown_server()  # oprește Flask

        return """
        ✅ Te-ai autentificat!<br><br>
        Poți închide fereastra.<br>
        Vezi terminalul pentru token-uri.
        """

    print("🚀 Starting login...")
    # global server_thread
    server_thread = threading.Thread(target=run_flask)
    server_thread.start()
    time.sleep(1)
    app.oauth_session = start_oauth_flow()
    login_done.wait()

    keyring.set_password(
        "eocube-cli", "offline-refresh-token", oauth_token["refresh_token"]
    )
    keyring.set_password("eocube-cli", "access-token", oauth_token["access_token"])
    print("🔐 Saved tokens in keyring")
    os._exit(0)


@auth_cli.command("logout")
def logout():
    """Logout from EOCloud.Ro"""

    refresh_token = keyring.get_password("eocube-cli", "offline-refresh-token")
    access_token = keyring.get_password("eocube-cli", "access-token")

    metadata = requests.get(ROCS_DISCOVERY_URL).json()
    revocation_endpoint = metadata.get("revocation_endpoint")
    if not revocation_endpoint:
        print("❌ IdP does not provide `revocation_endpoint` in metadata.")
        return

    client_id = EOCUBE_CLI_CLIENT_ID
    client_secret = None  # dacă e public client

    def revoke(token, token_type_hint):
        data = {
            "client_id": client_id,
            "token": token,
            "token_type_hint": token_type_hint,
        }
        if client_secret:
            data["client_secret"] = client_secret

        resp = requests.post(revocation_endpoint, data=data)
        if resp.status_code == 200:
            print(f"🔒 Token ({token_type_hint}) was revoked.")
        else:
            print(
                f"⚠️ Error revoking({token_type_hint}): {resp.status_code} {resp.text}"
            )

    if refresh_token:
        revoke(refresh_token, "refresh_token")
        keyring.delete_password("eocube-cli", "offline-refresh-token")
        print("🗑️ Refresh token was deleted.")

    if access_token:
        revoke(access_token, "access_token")
        keyring.delete_password("eocube-cli", "access-token")
        print("🗑️ Access token was deleted.")

    if not access_token and not refresh_token:
        print("ℹ️ No tokens in keyring.")

    print("✅ Logout complete.")


def get_storage_credentials_using_access_token(
    endpoint: str,
    token: str,
    duration_seconds: int = 3600,
    policy: str = None,
):
    params = {
        "Action": "AssumeRoleWithWebIdentity",
        "Version": "2011-06-15",
        "WebIdentityToken": token,
        "DurationSeconds": duration_seconds,
    }

    if policy is not None:
        params["Policy"] = policy

    response = requests.post(
        endpoint,
        data=urlencode(params),
        headers={"Content-Type": "application/x-www-form-urlencoded"},
    )
    root = ET.fromstring(response.text)
    ns = {"ns": "https://sts.amazonaws.com/doc/2011-06-15/"}
    creds = root.find(".//ns:Credentials", ns)
    if creds is None:
        print(response.text)
        raise Exception("Could not parse credentials from response")

    expiration_str = creds.find("ns:Expiration", ns).text
    expiration_dt = date_parser.parse(expiration_str)

    return StorageCredentials(
        endpoint=endpoint,
        access_key=creds.find("ns:AccessKeyId", ns).text,
        secret_key=creds.find("ns:SecretAccessKey", ns).text,
        session_token=creds.find("ns:SessionToken", ns).text,
        expiration=expiration_dt,
        leeway=300,
    )


@auth_cli.command("get-storage-credentials")
@click.option(
    "--endpoint",
    help="Storage endpoint",
    default="https://storage.svc.uvt-01.eocube.ro/",
)
def get_storage_credentials(endpoint):
    """Return S3 Credentials"""
    access_token = get_token()
    creds = get_storage_credentials_using_access_token(
        endpoint=endpoint, token=access_token
    )
    print(f"Access Key {creds.access_key}")
    print(f"Secret Key {creds.secret_key}")
