#!/usr/bin/env python3

# This file is part of the Trezor project.
#
# Copyright (C) 2012-2022 SatoshiLabs and contributors
#
# This library is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License version 3
# as published by the Free Software Foundation.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the License along with this library.
# If not, see <https://www.gnu.org/licenses/lgpl-3.0.html>.

import configparser
import os
import re
import sys

import pyotp
from trezorlib.client import TrezorClient
from trezorlib.misc import decrypt_keyvalue, encrypt_keyvalue
from trezorlib.tools import parse_path
from trezorlib.transport import get_transport
from trezorlib.ui import ClickUI

BIP32_PATH = parse_path("10016h/0")


def encrypt(type: str, domain: str, secret: str) -> str:
    transport = get_transport()
    client = TrezorClient(transport, ClickUI())
    dom = type.upper() + ": " + domain
    enc = encrypt_keyvalue(client, BIP32_PATH, dom, secret.encode(), False, True)
    client.close()
    return enc.hex()


def decrypt(type: str, domain: str, secret: bytes) -> bytes:
    transport = get_transport()
    client = TrezorClient(transport, ClickUI())
    dom = type.upper() + ": " + domain
    dec = decrypt_keyvalue(client, BIP32_PATH, dom, secret, False, True)
    client.close()
    return dec


class Config:
    def __init__(self) -> None:
        XDG_CONFIG_HOME = os.getenv("XDG_CONFIG_HOME", os.path.expanduser("~/.config"))
        os.makedirs(XDG_CONFIG_HOME, exist_ok=True)
        self.filename = XDG_CONFIG_HOME + "/trezor-otp.ini"
        self.config = configparser.ConfigParser()
        self.config.read(self.filename)

    def add(self, domain: str, secret: str, type: str = "totp") -> None:
        self.config[domain] = {}
        self.config[domain]["secret"] = encrypt(type, domain, secret)
        self.config[domain]["type"] = type
        if type == "hotp":
            self.config[domain]["counter"] = "0"
        with open(self.filename, "w") as f:
            self.config.write(f)

    def get(self, domain: str):
        s = self.config[domain]
        if s["type"] == "hotp":
            s["counter"] = str(int(s["counter"]) + 1)
            with open(self.filename, "w") as f:
                self.config.write(f)
        secret = decrypt(s["type"], domain, bytes.fromhex(s["secret"]))
        if s["type"] == "totp":
            return pyotp.TOTP(secret).now()
        if s["type"] == "hotp":
            c = int(s["counter"])
            return pyotp.HOTP(secret).at(c)
        return ValueError("unknown domain or type")


def add() -> None:
    c = Config()
    domain = input("domain: ")
    while True:
        secret = input("secret: ")
        if re.match(r"^[A-Z2-7]{16}$", secret):
            break
        print("invalid secret")
    while True:
        type = input("type (t=totp h=hotp): ")
        if type in ("t", "h"):
            break
        print("invalid type")
    c.add(domain, secret, type + "otp")
    print("Entry added")


def get(domain: str) -> None:
    c = Config()
    s = c.get(domain)
    print(s)


def main() -> None:
    if len(sys.argv) < 2:
        print("Usage: trezor-otp.py [add|domain]")
        sys.exit(1)
    if sys.argv[1] == "add":
        add()
    else:
        get(sys.argv[1])


if __name__ == "__main__":
    main()