import sys
from datetime import datetime

import pyotp
from urllib.parse import urlparse, parse_qs, unquote, urlencode, urlunparse
import hashlib


class OTPUrl:
    def __init__(self, url):
        self.url = url
        parts = urlparse(url)
        if parts.scheme != 'otpauth':
            raise ValueError("Not an otpauth:// url")

        self.type = parts.hostname

        if self.type not in ['totp', 'hotp']:
            raise ValueError("Unknown key type '{}'".format(self.type))

        self.label = unquote(parts.path[1:])

        qs = parse_qs(parts.query)
        self.secret = qs['secret'][0]
        self.issuer = qs['issuer'][0] if 'issuer' in qs else None
        self.period = int(qs['period'][0]) if 'period' in qs else 30
        self.digits = int(qs['digits'][0]) if 'digits' in qs else 6
        self.initial_count = int(qs['counter'][0]) if 'counter' in qs else 0
        if 'initial_count' in qs:
            self.initial_count = int(qs['initial_count'][0])

        self.digest = hashlib.sha1
        if 'digest' in qs:
            digest = qs['digest'][0]
            if hasattr(hashlib, digest):
                self.digest = getattr(hashlib, digest)

    @classmethod
    def create(cls, name, type, secret, duration=None, length=6, counter=None):
        if type == 'totp':
            otp = pyotp.TOTP(secret, interval=duration, digits=length)
        elif type == 'hotp':
            otp = pyotp.HOTP(secret, initial_count=counter, digits=length)
        return cls(otp.provisioning_uri(name=name))

    def get_token(self):
        if self.type == 'totp':
            totp = pyotp.TOTP(self.secret, interval=self.period, digits=self.digits, digest=self.digest)
            return totp.now(), totp.interval - datetime.now().timestamp() % totp.interval
        elif self.type == 'hotp':
            hotp = pyotp.HOTP(self.secret, digits=self.digits, digest=self.digest, initial_count=self.initial_count)
            return hotp.at(0), None
        else:
            sys.stderr.write("Unknown key format '{}'\n".format(self.type))
            return None, None

    def get_url(self):
        properties = {
            'secret': self.secret,
            'issuer': self.issuer,
            'digits': self.digits
        }
        if self.type == 'totp':
            properties['period'] = self.period
        elif self.type == 'hotp':
            properties['counter'] = self.initial_count
        qs = urlencode(properties, doseq=True)
        url = urlunparse(['otpauth', self.type, '/' + self.label, '', qs, ''])
        return url

    def __repr__(self):
        return '<OTPUrl {} {} from {}>'.format(self.type, self.label, self.issuer)
