"Project class structure"

import asyncio
import datetime
import json
import logging
import socket
from typing import Any, Dict, Optional
from uuid import uuid4

from pydantic.v1 import BaseModel, Field, root_validator, validator

from services.company_profile.company_profile import CompanyProfileMaker
from services.company_profile.data_classes.company_info import CompanyInfo
from services.ppt_generator.data_classes.presentation_outline import SlideContent
from utils.dynamo_db import DynamoDB
from utils.url_parser import parsed_url


def build_extra(cls, values):
    "add extra information if needed."
    all_required_field_names = {
        field.alias for field in cls.__fields__.values() if field.alias != "extra"
    }  # to support alias

    extra: Dict[str, Any] = {}
    for field_name in list(values):
        if field_name not in all_required_field_names:
            if values[field_name] is not None and field_name != "extra":
                extra[field_name] = values.pop(field_name)
    return extra if extra != {} else None


class Slide(BaseModel, extra="allow"):
    "Slide structure."
    uuid: str = Field(
        default_factory=lambda: str(uuid4().hex), title="The UUID of the slide"
    )
    title: str = Field(..., title="The title of the slide")
    content: str = Field(..., title="The content of the slide")
    data: str = Field(..., title="Data required for the slide to complete")
    design: str = Field(
        ...,
        title="""Design for the powerpoint slide with respect to the data
         and content assume a 16:9 aspect ratio and 1920x1080 resolution.""",
    )
    questions: list[str] = Field(
        ..., title="Questions needed to be answered to complete the slide"
    )
    slide_order: int = Field(..., title="The order of the slide in the section")
    content_layout: SlideContent = Field(default=None, title="Slide content structure")
    research: Optional[str] = Field(
        default=None, title="The research done for the slide"
    )
    isNew: bool = Field(
        default=False, title="Whether the slide is new"
    )  # only for front use case when a new slide is created
    isUpdated: bool = Field(
        default=False, title="Whether the slide is updated"
    )  # only for front use case when a new slide is created

    @root_validator(pre=True)
    @classmethod
    def build_extra(cls, values):
        "add extra information if needed."
        values["extra"] = build_extra(cls, values)
        return values

    @staticmethod
    def build_slide_research_id(project_id: str, section_id: str, slide_id: str) -> str:
        return project_id + "-" + section_id + "-" + slide_id

    def get_slide_research(self):
        "Get the research done for the slide."
        db = DynamoDB()
        item = db.get_item(db.get_table("slide_research"), self.research)
        return item.get("research", "")


class Section(BaseModel, extra="allow"):
    "Section structure."
    uuid: str = Field(
        default_factory=lambda: str(uuid4().hex), title="The UUID of the section"
    )
    title: str = Field(..., title="The title of the section")
    slides: list[Slide] | None = Field(
        default=None,
        title="list of slides for the section, include 3 or more slides per section",
    )
    section_order: int = Field(
        ..., title="The order of the section in the presentation"
    )
    content: str | None = Field(default=None, title="The content of the section")
    isNew: bool = Field(default=False, title="Whether the section is new")
    isUpdated: bool = Field(default=False, title="Whether the section is updated")

    @root_validator(pre=True)
    @classmethod
    def build_extra(cls, values):
        "add extra information if needed."
        values["extra"] = build_extra(cls, values)
        return values


class Project(BaseModel, extra="allow"):
    "Project structure."
    project_id: str = Field(..., title="The project id")
    company_url: str = Field(default=None, title="The company url")
    company_name: str = Field(default=None, title="The company name")
    company_alt_names: list[str] | None = Field(
        default=[], title="The company alternative names"
    )

    final_ppt_path: str | None = Field(default=None, title="The final ppt path")

    industry: str | None = Field(default=None, title="The industry of the company")
    sector: str | None = Field(default=None, title="The sector of the company")
    public_status: str | None = Field(
        default="private", title="The public status of the company"
    )
    pitch_type: str | None = Field(default=None, title="The pitch type of the project")

    company_financials: list | None = Field(
        default=[], title="The financials for the company"
    )

    status: str | None = Field(
        default="active",
        title="The status of the project (i.e.: active, archived, deleted)",
    )

    team_members: list[str] | None = Field(
        default=None, title="The team members of the project"
    )
    sections: list[Section] = Field(
        default=None, title="list of sections in the presentation"
    )

    targets: list[str] | None = Field(default=None, title="The targets for the project")
    public_comps: list[str] | None = Field(
        default=None, title="The public comps for the project"
    )

    client: str | None = Field(default=None, title="The client of the project")
    created_by: str | None = socket.gethostname()
    created_date: str | None = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    modified_by: str | None = socket.gethostname()
    modified_date: str | None = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    pitch_date: str | None = Field(default=None, title="The pitch date of the project")

    def stringify_company_info(self, include_project_info: bool = False):
        "Convert the company info to a string"

        if include_project_info:
            return json.dumps(self.dict())
        return json.dumps(
            {
                "company_name": self.company_name,
                "company_url": self.company_url,
                "industry": self.industry,
                "sector": self.sector,
                "public_status": self.public_status,
                "pitch_type": self.pitch_type,
                "company_alt_names": self.company_alt_names,
                "company_financials": self.company_financials,
                "pitch_date": self.pitch_date,
            }
        )

    def update_modify(self):
        "Update the modified date and modified by"
        self.modified_by = socket.gethostname()
        self.modified_date = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")

    @validator("company_name", always=True)
    @classmethod
    def company_name_format(cls, v):
        "Convert the company name to title case"
        return v.title()

    @root_validator(pre=True)
    @classmethod
    def set_parameter_values(cls, field_values):
        "Set the parameter values for the project"

        if field_values["company_url"] is None:
            raise ValueError("Company url is required for new project")

        # get the company info from the company url in the DB
        company_url = parsed_url(field_values["company_url"]).url
        company_info = CompanyInfo.get_company_info(company_url)

        if company_info:
            if field_values["company_name"] is None:
                field_values["company_name"] = company_info.company_name
            if field_values["industry"] is None:
                field_values["industry"] = company_info.industry
            if field_values["sector"] is None:
                field_values["sector"] = company_info.sector
            if field_values["public_status"] is None:
                field_values["public_status"] = (
                    "public"
                    if company_info.stock_ticker is not None
                    or company_info.stock_ticker != ""
                    else "private"
                )

        field_values = cls.build_extra(field_values)

        return field_values

    @staticmethod
    def check_project_in_db(project_id: str = None):
        "Check the DB for the project structure."
        if project_id:
            db = DynamoDB()
            response = db.get_item(db.projects, project_id)
            ## add in the response of
            project_structure_response = db.get_item(db.project_structure, project_id)
            if project_structure_response:
                response["sections"] = project_structure_response["sections"]
            if response:
                return Project(**response)

        return None

    # update the project in the DB
    # run this if the class is modified
    def update_project_in_db(self):
        "Update the project in the DB"
        self.update_modify()
        db = DynamoDB()
        ## split the project sections into a separate table and project information into a separate table
        project_structure_response = None
        if self.sections is not None and isinstance(self.sections, list):
            project_structure = {
                "project_id": self.project_id,
                "sections": [s.dict() for s in self.sections],
                "created_by": self.created_by,
                "created_date": self.created_date,
                "modified_by": self.modified_by,
                "modified_date": self.modified_date,
            }
            project_structure_response = db.upload_to_dynamodb(
                db.project_structure, project_structure
            )
            # project information without the sections
        project_info = self.dict(exclude={"sections"})
        project_response = db.upload_to_dynamodb(db.projects, project_info)

        return [project_response, project_structure_response]

    @classmethod
    def build_extra(cls, values):
        "add extra information if needed."
        values["extra"] = build_extra(cls, values)
        return values

    # assign the company name as one of the company alt names
    @root_validator
    @classmethod
    def assign_company_name_as_alt_name(cls, values):
        "assign the company name as one of the company alt names"
        if values["company_name"] not in values["company_alt_names"]:
            values["company_alt_names"].append(values["company_name"])
        return values

    @classmethod
    def verify_target(cls, targets: list[str]):
        "Verify the targets for the project"
        # check if the targets are valid companies
        for target in targets:
            try:
                company_url = parsed_url(target).url
                company_info = CompanyInfo.get_company_info(company_url)

            except ValueError:
                try:
                    cpm = CompanyProfileMaker(company_url)
                    asyncio.run(cpm.main())
                except Exception as e2:
                    logging.error(
                        "Error creating company profile for %s: %s", target, e2
                    )

    @classmethod
    def update_project(cls, project):
        "Update the project"
        project_in_db = Project.check_project_in_db(project.project_id)

        # Get the difference between the project and the current item
        updated_fields = {}
        for field, value in project.dict().items():
            if getattr(project_in_db, field) != value:
                updated_fields[field] = value

        # Update the project in the DB
        project.update_project_in_db()
        return updated_fields

    def convert_project_to_markdown(self):
        sections = self.sections or []
        markdown_string = "\n\n\n".join(
            f"# Section: {section.title}\n\n"
            + "\n".join(
                f"## Slide: {slide.title}\n### Research: \n{slide.research}\n"
                for slide in section.slides or []
            )
            for section in sections
        )
        # print(markdown_string)
        return markdown_string
