#!/usr/bin/python3
import functools
import itertools
import pathlib
import re
from urllib.parse import urlparse

from pnk.helpers import logger

log = logger.get_logger()
functools.partial(print, flush=True)


class Formula:
    def __init__(self, args, file, wordlist=None) -> None:
        self.args = args  # optional arguments stored here
        self.file = file
        self.wordlist = wordlist

    def parse_hostname(self, hostname, target=None):
        """Extract a domain and subdomains from the input"""
        log.debug(f"{target=}")
        url = urlparse(hostname)
        log.debug(f"{url=}")
        host = url.hostname or url.geturl()
        log.debug(f"{host=}")
        if host:
            pattern = (
                rf"{target}$"
                if target
                else r"[\w-]+[.](рф|com|co.uk|ru|org|co|in|ai|sh|io|jp|com.cn|cn|cz|de|net|fr|it|au|ca|ir|br|com.br|co.kr|gov|uk|kz|tech|shop|moscow|store|me)$"
            )
            _domain = re.search(
                pattern,
                host,
            )
            log.debug(f"{_domain=}")
            if not _domain:
                raise TypeError(f"No domain found for {host}")
            _subdomains = host[: _domain.start()] + host[_domain.end() :]
            if not _subdomains:
                raise TypeError(f"No subdomains found for {host}")
            domain = _domain.group(0)
            log.debug(f"{domain=}")
            subdomains = _subdomains.split(".")[:-1]
            log.debug(f"{subdomains=}")
            return host, domain, subdomains
        else:
            raise TypeError(f"No host found for {host}")

    def pnk(self, subdomains):
        """Sequence of permutations on subdomains"""
        log.debug("Permutation...")
        for p in itertools.permutations(subdomains):
            yield p

    def replacer(self, subdomains):
        """Replace `-` found in subdomain with dots and vice versa"""
        log.debug("Replace dashes with dots")
        pattern = re.compile("-")
        _deque = []
        counter = 0
        for s in subdomains:
            _arr = pattern.split(s)
            log.debug(_arr)
            if len(_arr) == 1:
                log.debug("No dashes found!")
                _deque.append(_arr)
                log.debug(f"{_deque=}")
            else:
                _deque.append(itertools.product(*self.add_template(_arr)))
                log.debug(f"{_deque=}")
                counter = counter + 1
        # remove duplicates generated by pnk
        # stdout only if there are dashes found
        if counter > 0:
            return itertools.product(*_deque)
        else:
            return ()

    def incrmt(self, subdomain):
        """Increment a digit found in subdomain"""
        log.debug(f"Increment digits on {subdomain=}")
        pattern = re.compile("(?<!\d)\d{1,2}(?!\d)")
        counter = 0
        for match in pattern.finditer(subdomain):
            counter = counter + 1
            log.debug(match)
            if match:
                m_start = match.start()
                m_end = match.end()
                range_count = 10 if len(match.group()) < 2 else 100
                for i in range(range_count):
                    _s = (
                        subdomain[:m_start]
                        + str(i).zfill(len(subdomain[m_start:m_end]))
                        + subdomain[m_end:]
                    )
                    log.debug(_s)
                    yield _s
                # check for duplicated digits to increment it both
                if (
                    match.group() in subdomain[:m_start]
                    and match.group() not in subdomain[m_end:]
                ):
                    log.debug("duplicated digits")
                    log.debug(match)
                    for i in range(range_count):
                        _sd = subdomain.replace(
                            subdomain[m_start:m_end],
                            str(i).zfill(len(subdomain[m_start:m_end])),
                        )
                        log.debug(_sd)
                        yield _sd
        if counter == 0:
            log.debug("No digits found!")
            return ()

    def crtsn(self, subdomains):
        log.debug("Cartesian...")
        is_process = False
        _deque = []
        subs_list = []
        pattern = re.compile("((?<!\d)\d{1,2}(?!\d))")
        for _s in subdomains:
            log.debug(_s)
            _list = pattern.split(_s)
            if len(_list) == 1:
                log.debug(_list)
                _deque.append(_list)
            else:
                is_process = True
                log.debug(_list)
                for x in _list:
                    try:
                        _integer = int(x)
                        if _integer or x in ("0", "00"):
                            log.debug(f"{x=}")
                            x_lenght = len(x)
                            if x_lenght <= 2:
                                _deque.append(
                                    [
                                        str(i).zfill(x_lenght)
                                        for i in range(10 if x_lenght == 1 else 100)
                                    ]
                                )
                            else:
                                _deque.append([x])
                    except ValueError:
                        # filter out empty str
                        if x != "":
                            _deque.append([x])

            subs_list.append(itertools.product(*_deque))
            log.debug(_deque)
            log.debug(subs_list)
            _deque.clear()
        if is_process:
            return itertools.product(*subs_list)
        else:
            return ()

    def join_product_tuples(self, tuples, delimeter=None):
        """Convert a list of tuples into a string
        input: (('1', '_', 'n', '-', '0', '_', 'test1'), ('s2', '-', 'v'))
        output: 1_n-0_test.s2-v
        """
        dlmtr = delimeter if delimeter else "."
        _len = len(tuples)
        log.debug(_len)
        log.debug(tuples)
        if _len == 1:
            return "".join(*tuples)
        return "".join(tuples[0]) + dlmtr + self.join_product_tuples(tuples[1:])

    def add_template(self, items):
        log.debug(items)
        if len(items) == 1:
            return [items]
        _list = []
        _list.append([items[0]])
        _list.append(["-", "_", "."])
        _list.extend(self.add_template(items[1:]))
        return _list

    def produce_wordlist(self, subdomains):
        """TODO: see https://github.com/storenth/pnk/issues/1
        Read the wordlist and return lines generator
        """
        wordlist = (
            self.args.wordlist
            if self.args.wordlist
            else pathlib.Path(__file__).parent / "wordlist.txt"
        )
        for word in wordlist:
            log.debug(f"{word.strip()=}")
            yield self.pnk([word.strip(), *subdomains])

    def run(self):
        """Compose functions on files with hostname lines"""
        for lines in self.file:
            for line in lines:
                log.debug(line.strip())
                try:
                    h, d, s = (
                        (None, None, line.strip().split("."))
                        if self.args.data
                        else (
                            self.parse_hostname(line.strip(), self.args.target)
                            if self.args.target
                            else self.parse_hostname(line.strip())
                        )
                    )
                except TypeError as err:
                    # ignore not hostname and lack of subdomains cases
                    log.debug(f"{err=}")
                else:
                    if self.args.increment:
                        _s = s.copy()
                        for index, j in enumerate(s):
                            for i in self.incrmt(j):
                                _s[index] = i
                                print(".".join(filter(None, [".".join(_s), d])))
                            _s[index] = j

                    if self.args.cartesian:
                        for c in self.crtsn(s):
                            log.debug(c)
                            print(
                                ".".join(filter(None, [self.join_product_tuples(c), d]))
                            )

                    if self.args.replace:
                        for x in self.replacer(s):
                            log.debug(x)
                            print(
                                ".".join(filter(None, [self.join_product_tuples(x), d]))
                            )

                    if self.args.wordlist:
                        for word_subs_permutations in self.produce_wordlist(s):
                            for p in word_subs_permutations:
                                log.debug(p)
                                print(
                                    ".".join(
                                        filter(None, [self.join_product_tuples(p), d])
                                    )
                                )
                                print(
                                    ".".join(
                                        filter(
                                            None, [self.join_product_tuples(p, "-"), d]
                                        )
                                    )
                                )

                    # region operations by default
                    for p in self.pnk(s):
                        log.debug(p)
                        print(".".join(filter(None, [".".join(p), d])))
                    # endregion
