import asyncio
import datetime
import os
import warnings

from secedgar.cik_lookup import CIKLookup
from secedgar.client import NetworkClient
from secedgar.exceptions import FilingTypeError
from secedgar.filings._base import AbstractFiling
from secedgar.filings.filing_types import FilingType
from secedgar.utils import sanitize_date


class Filing(AbstractFiling):
    """Base class for receiving EDGAR filings.

    Args:
        cik_lookup (str): Central Index Key (CIK) for company of interest.
        filing_type (secedgar.filings.filing_types.FilingType): Valid filing type enum.
        start_date (Union[str, datetime.datetime], optional): Date before which not to
            fetch reports. Stands for "date after."
            Defaults to None (will fetch all filings before end_date).
        end_date (Union[str, datetime.datetime], optional): Date after which not to fetch reports.
            Stands for "date before." Defaults to today.
        count (int): Number of filings to fetch. Will fetch up to `count` if that many filings
            are available. Defaults to all filings available.
        kwargs: See kwargs accepted for :class:`secedgar.client.network_client.NetworkClient`.

    .. versionadded:: 0.1.5
    """

    def __init__(self,
                 cik_lookup,
                 filing_type,
                 start_date=None,
                 end_date=datetime.datetime.today(),
                 client=None,
                 count=None,
                 **kwargs):
        # Leave params before other setters
        self._params = {
            'action': 'getcompany',
            'output': 'xml',
            'owner': 'include',
            'start': 0,
        }
        self.start_date = start_date
        self.end_date = end_date
        self.filing_type = filing_type
        # make CIKLookup object for users if not given
        self.cik_lookup = cik_lookup
        self.count = count
        # Make default client NetworkClient and pass in kwargs
        self._client = client if client is not None else NetworkClient(**kwargs)

    @property
    def path(self):
        """str: Path added to client base."""
        return "cgi-bin/browse-edgar"

    @property
    def params(self):
        """:obj:`dict`: Parameters to include in requests."""
        return self._params

    @property
    def client(self):
        """``secedgar.client._base``: Client to use to make requests."""
        return self._client

    @property
    def start_date(self):
        """Union([datetime.datetime, str]): Date before which no filings are fetched."""
        return self._start_date

    @start_date.setter
    def start_date(self, val):
        if val is not None:
            self._start_date = val
            self._params['datea'] = sanitize_date(val)
        else:
            self._start_date = None

    @property
    def end_date(self):
        """Union([datetime.datetime, str]): Date after which no filings are fetched."""
        return self._end_date

    @end_date.setter
    def end_date(self, val):
        self._end_date = val
        self._params['dateb'] = sanitize_date(val)

    @property
    def filing_type(self):
        """``secedgar.filings.FilingType``: FilingType enum of filing."""
        return self._filing_type

    @filing_type.setter
    def filing_type(self, filing_type):
        if not isinstance(filing_type, FilingType):
            raise FilingTypeError
        self._filing_type = filing_type
        self._params['type'] = filing_type.value

    @property
    def count(self):
        """Number of filings to fetch."""
        return self._count

    @count.setter
    def count(self, val):
        if val is None:
            self._count = None
        elif not isinstance(val, int):
            raise TypeError("Count must be positive integer or None.")
        elif val < 1:
            raise ValueError("Count must be positive integer or None.")
        else:
            self._count = val
            self._params['count'] = val

    @property
    def cik_lookup(self):
        """``secedgar.cik_lookup.CIKLookup``: CIKLookup object."""
        return self._cik_lookup

    @cik_lookup.setter
    def cik_lookup(self, val):
        if not isinstance(val, CIKLookup):
            val = CIKLookup(val)
        self._cik_lookup = val

    def get_urls(self, **kwargs):
        """Get urls for all CIKs given to Filing object.

        Args:
            **kwargs: Anything to be passed to requests when making get request.
                See keyword arguments accepted for
                ``secedgar.client._base.AbstractClient.get_soup``.

        Returns:
            urls (list): List of urls for txt files to download.
        """
        return {
            key: self._get_urls_for_cik(cik, **kwargs)
            for key, cik in self.cik_lookup.lookup_dict.items()
        }

    # TODO: Change this to return accession numbers that are turned into URLs later
    def _get_urls_for_cik(self, cik, **kwargs):
        """Get all urls for specific company according to CIK.

        Must match start date, end date, filing_type, and count parameters.

        Args:
            cik (str): CIK for company.
            **kwargs: Anything to be passed to requests when making get request.
                See keyword arguments accepted for
                ``secedgar.client._base.AbstractClient.get_soup``.

        Returns:
            txt_urls (list of str): Up to the desired number of URLs for that specific company
            if available.
        """
        self.params['CIK'] = cik
        links = []
        self.params["start"] = 0  # set start back to 0 before paginating

        while self.count is None or len(links) < self.count:
            data = self.client.get_soup(self.path, self.params, **kwargs)
            links.extend([link.string for link in data.find_all("filinghref")])
            self.params["start"] += self.client.batch_size
            if len(data.find_all("filinghref")) == 0:  # no more filings
                break

        txt_urls = [link[:link.rfind("-")].strip() + ".txt" for link in links]

        if isinstance(self.count, int) and len(txt_urls) < self.count:
            warnings.warn("Only {num} of {count} filings were found for {cik}.".format(
                num=len(txt_urls), count=self.count, cik=cik))

        # Takes `count` filings at most
        return txt_urls[:self.count]

    def save(self, directory, dir_pattern=None, file_pattern=None):
        """Save files in specified directory.

        Each txt url looks something like:
        https://www.sec.gov/Archives/edgar/data/1018724/000101872419000043/0001018724-19-000043.txt

        Args:
            directory (str): Path to directory where files should be saved.
            dir_pattern (str): Format string for subdirectories. Default is "{cik}/{type}".
                Valid options are {cik} and/or {type}.
            file_pattern (str): Format string for files. Default is "{accession_number}".
                Valid options are {accession_number}.

        Returns:
            None

        Raises:
            ValueError: If no text urls are available for given filing object.
        """
        urls = self._check_urls_exist()

        if dir_pattern is None:
            dir_pattern = os.path.join('{cik}', '{type}')
        if file_pattern is None:
            file_pattern = '{accession_number}'

        inputs = []
        for cik, links in urls.items():
            formatted_dir = dir_pattern.format(cik=cik, type=self.filing_type.value)
            for link in links:
                formatted_file = file_pattern.format(
                    accession_number=self.get_accession_number(link))
                path = os.path.join(directory,
                                    formatted_dir,
                                    formatted_file)
                inputs.append((link, path))

        loop = asyncio.get_event_loop()
        loop.run_until_complete(self.client.wait_for_download_async(inputs))
