# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Module for performing parsing of external entities.

External entity represents a YouTube Channel or Video, Webpage,
Mobile Application or anything that requires connection to external
(non Google Ads) system in order to get information from it.
"""

from __future__ import annotations

import dataclasses
import itertools
import logging

from gaarf import report

from googleads_housekeeper.adapters import repository
from googleads_housekeeper.domain import external_parsers
from googleads_housekeeper.domain.core import exclusion_specification
from googleads_housekeeper.services import unit_of_work

PARSER_MAPPING = {
  'youtube_channel_info': external_parsers.youtube_data_parser.ChannelInfoParser,
  'youtube_video_info': external_parsers.youtube_data_parser.VideoInfoParser,
  'website_info': external_parsers.website_parser.WebSiteParser,
}


@dataclasses.dataclass
class ParseOptions:
  """Specifies options regarding parsing behaviour.

  Attributes:
      save_to_db: Whether to save parsing results to DB.
      batch_size: Maximum number of entities in response.
  """

  save_to_db: bool = True
  batch_size: int = 50


class ExternalEntitiesParser:
  """Performance parsing of external entities (YouTube, Websites, etc.).

  Attributes:
      uow: Unit of work to handle transaction.
  """

  def __init__(self, uow: unit_of_work.AbstractUnitOfWork) -> None:
    """Initializes an instance of ExternalEntitiesParser.

    Args:
        uow: Unit of work to handle transaction.
    """
    self.uow = uow

  def parse_specification_chain(
    self,
    entities: report.GaarfReport,
    specification: exclusion_specification.ExclusionSpecification,
    parse_options: ParseOptions = ParseOptions(),
  ) -> None:
    """Performance parsing of entities via all parsers in specification.

    Args:
        entities:
            Report containing entities that should be parsed.
        specifications:
            All possible non Ads specifications. Entities within a report
            can be parsed via different parsers (YouTube, Website, etc.)
        parse_options:
            Options for performing parsing.
    """
    with self.uow as uow:
      for specification_entries in specification.specifications:
        for specification_entry in specification_entries:
          self._parse_via_external_parser(
            entities=entities,
            specification_entry=specification_entry,
            uow=uow,
            parse_options=parse_options,
          )

  def _parse_via_external_parser(
    self,
    entities: report.GaarfReport,
    specification_entry: exclusion_specification.BaseExclusionSpecificationEntry,
    uow: unit_of_work.AbstractUnitOfWork,
    parse_options: ParseOptions,
  ) -> None:
    """Performance parsing of entities via a single parser in specification.

    Args:
        entities:
            Report containing entities that should be parsed.
        specification:
            Single non Ads specifications. Entities will be parsed via
            a single parsers (YouTube, Website, etc.
        uow:
            Unit of work to handle transaction.
        parse_options:
            Options for performing parsing.
    """
    repo = getattr(uow, specification_entry.repository_name)
    parser = PARSER_MAPPING.get(specification_entry.repository_name)
    if not_parsed_entities := self._get_not_parsed_entities(
      entities, specification_entry, repo
    ):
      i = 0
      while batch := list(
        itertools.islice(not_parsed_entities, i, i + parse_options.batch_size)
      ):
        parsed_entities_info = parser().parse(batch)
        i += parse_options.batch_size
        if parse_options.save_to_db:
          for parsed_placement in parsed_entities_info:
            logging.debug('saving placement: %s to db', parsed_placement)
            repo.add(parsed_placement)
          uow.commit()

  def _get_not_parsed_entities(
    self,
    entities: report.GaarfReport,
    specification_entry: exclusion_specification.BaseExclusionSpecificationEntry,
    repo: repository.AbstractRepository,
  ) -> list[str]:
    """Get all entities not parsed and saved in DB.

    Args:
        entities:
            Report containing entities that should be parsed.
        specification_entry:
            Single non Ads specifications. Entities will be parsed via
            a single parsers (YouTube, Website, etc.).
        repo:
            Repository to get data from.

    Returns:
        Non-parsed placements.
    """
    not_parsed_entities: list[str] = []
    for entity_info in entities:
      if (
        entity_info.placement_type
        == specification_entry.corresponding_placement_type.name
      ):
        if not repo.get_by_conditions(
          {
            'placement': entity_info.placement,
          }
        ):
          not_parsed_entities.append(str(entity_info.placement))
    return not_parsed_entities
