"""SEC filing archive and document handling."""

import lzma
import os
import sys
from typing import Any

from bs4 import BeautifulSoup

from secbulkdownload.config import default_datadir, sec_base_url
from secbulkdownload.limiter import rate_limiter
from secbulkdownload.secmeta import headerfile
from secbulkdownload.utils import fetch_sec_file, seclimiter


class filingDocument:
    """
    Represents an SEC filing document within an archive.

    Attributes:
        filename: Name as it appears on SEC website
        type: File type field from archive
        sequence: Sequence number in archive
        description: File description field
        body: File content stripped of HTML markup
        html: Complete file contents (BeautifulSoup element)
    """

    def __init__(
        self,
        filename: str,
        file_type: str,
        sequence: str,
        description: str,
        body: str,
        html: Any,
    ) -> None:
        self.filename = filename
        self.type = file_type
        self.sequence = sequence
        self.description = description
        self.body = body
        self.html = html


class filingArchive:
    """
    Interface to SEC filing archive containing related documents.

    Required arguments:
        sec_filepath: Path to filing archive from SEC index
        user_agent: SEC User-Agent string

    Optional arguments:
        datadir: Directory for cached files
        ratelimiter: Rate limiter instance
        binary_file_types: Binary file extensions
    """

    def __init__(
        self,
        sec_filepath: str,
        datadir: str = default_datadir,
        ratelimiter: rate_limiter = seclimiter,
        user_agent: str | None = None,
        binary_file_types: list[str] | None = None,
    ) -> None:
        self.user_agent = user_agent
        self.binary_types = binary_file_types or ["gz", "zip", "Z"]
        self.limiter = ratelimiter
        self.sec_filepath = sec_filepath
        self.files: list[filingDocument] = []
        self.acceptance_datetime: str = ""
        self.doc_types: str | list[str] = "ALL"
        self.text_only: bool = True

        # Parse the SEC filepath to extract CIK and filename
        # Format: edgar/data/CIK/ACCESSION-NUMBER.txt
        _, _, cik, fname = self.sec_filepath.split("/")

        # Build the local path for the cached filing archive
        # Files are stored with .txt.xz extension (LZMA compressed)
        self.filingsdir = os.path.join(datadir, "filings")
        self.localfilename = os.path.join(self.filingsdir, cik, f"{fname}.xz")

        # Build the SEC URL for fetching
        self.filingURL = sec_base_url + self.sec_filepath

        # Create headerfile instance for metadata
        self.header = headerfile(
            self.sec_filepath, datadir=datadir, user_agent=self.user_agent
        ).get_headerDict()

    def check_localfile(self) -> bool:
        """Verify that file is present in the local cache."""
        return os.path.exists(self.localfilename)

    def _read_local_archive(self) -> str:
        """Read and decompress the local LZMA-compressed archive file."""
        with open(self.localfilename, "rb") as f:
            compressed_data = f.read()
        return lzma.decompress(compressed_data).decode("latin-1")

    def _fetch_archive(self, verbose: bool = False) -> str:
        """Fetch the archive from SEC and return the content."""
        result = fetch_sec_file(
            self.filingURL,
            self.localfilename,
            content=True,
            limiter=self.limiter,
            user_agent=self.user_agent,
            verbose=verbose,
        )
        # When content=True, fetch_sec_file returns tuple[str, bytes]
        _, data = result  # type: ignore[misc]
        return data.decode("latin-1")

    def get_filingArchive(
        self,
        document_types: str | list[str] = "ALL",
        text_only: bool = True,
        verbose: bool = False,
    ) -> None:
        """
        Retrieve archive and create filing document objects.

        Args:
            document_types: Filter by document types ('ALL' or list of types)
            text_only: If True, only include text files (txt, htm, html, XML)
            verbose: Print progress information
        """
        self.doc_types = document_types
        self.text_only = text_only

        # Text file suffixes to include when text_only is True
        # Use lowercase for case-insensitive matching
        text_suffixes = {"txt", "htm", "html", "xml", "xsd"}

        try:
            # Read locally cached filing if it exists, or fetch from SEC
            if self.check_localfile():
                data = self._read_local_archive()
            else:
                data = self._fetch_archive(verbose=verbose)

            # Parse with BeautifulSoup using lxml parser
            soup = BeautifulSoup(data, "lxml")

            # Extract acceptance-datetime if present
            acceptance_tag = soup.find("acceptance-datetime")
            if acceptance_tag and acceptance_tag.contents:
                self.acceptance_datetime = str(acceptance_tag.contents[0]).strip()[:14]

            # Reset files list
            self.files = []

            # Process each <document> tag
            for doc in soup.find_all("document"):
                # Extract filename
                filename_tag = doc.find("filename")
                fn = str(filename_tag.contents[0]).strip() if filename_tag else "_"

                # Extract document type
                type_tag = doc.find("type")
                doc_type = str(type_tag.contents[0]).strip() if type_tag else "_"

                # Extract sequence
                sequence_tag = doc.find("sequence")
                sequence = str(sequence_tag.contents[0]).strip() if sequence_tag else "_"

                # Extract description
                description_tag = doc.find("description")
                description = (
                    str(description_tag.contents[0]).strip() if description_tag else "_"
                )

                # Extract text content
                text_tag = doc.find("text")
                text = text_tag.get_text() if text_tag else "_"

                # Determine if this document should be included
                include_by_type = (
                    document_types == "ALL"
                    or doc_type == document_types
                    or (
                        isinstance(document_types, list)
                        and doc_type in document_types
                    )
                )

                include_by_suffix = (
                    not text_only
                    or fn.split(".")[-1].lower() in text_suffixes
                    or fn.split(".")[-1] in text_suffixes
                )

                if include_by_type and include_by_suffix:
                    self.files.append(
                        filingDocument(fn, doc_type, sequence, description, text, doc)
                    )

            if verbose:
                for f in self.files:
                    print(f.filename, f.type)

        except Exception:
            print(self.localfilename, self.filingURL)
            e = sys.exc_info()[0]
            print(f"Error: {e}")

    def get_filenames(self) -> list[str]:
        """Return list of filenames in the archive."""
        return [f.filename for f in self.files]

    def get_documents_by_type(self, doc_type: str) -> list[filingDocument]:
        """Return all documents matching the specified type."""
        return [f for f in self.files if f.type == doc_type]
