from typing import List, Optional, Dict, Any, Literal
from ._private._field_dto import FieldDtoWithHierarchy
from .well_production_enum import WellProductionTypeEnum
from .well import Well
from .well_group import WellGroup
from .file import File
from .document import Document
from ._private._cluster_apis import ClusterAPIS
from datetime import datetime
from .field_data_types_catalog import FieldDataTypesCatalog
from .field_well_properties_catalog import FieldWellPropertiesCatalog
from ._private.dto_converters._field_dto_converter import FieldDtoConverter
from .file_folder import FileFolder
from .file_folders_extensions import find_file_folder_recursively_by_id
from .pvt.pvt import PVT
from .gas_oil_type_enum import GasOilTypeEnum
from .unit_system_pvt_enum import UnitSystemPvtEnum
from .artm_model_enum import ARTMModelEnum
from .artm_improve_target_enum import ARTMImproveTargetEnum
from .artm_regression_algorithm_enum import ARTMRegressionAlgorithmEnum
from .artm_forecast_type_enum import ARTMForecastTypeEnum
[docs]
class Field:
    """ Field object.
    Presents a KAPPA Automate field object that can be queried for contained wells.
    Returned as a result of the :meth:`Connection.get_fields` query.
    .. note:: Should not be instantiated directly.
    .. note:: :py:obj:`Field.wells` property is populated on-demand and is cached for the duration of the :class:`Connection`.
    """
[docs]
    def __init__(self, field_id: str, name: str, asset: str, field_dto: Optional[FieldDtoWithHierarchy], cluster_apis: ClusterAPIS, dto_converter: FieldDtoConverter) -> None:
        self.__id: str = field_id
        self.__name: str = name
        self.__asset: str = asset
        self.__reference_date: Optional[datetime] = None
        self.__field_dto: Optional[FieldDtoWithHierarchy] = field_dto
        self.__wells: Optional[List[Well]] = None
        self.__well_groups: Optional[List[WellGroup]] = None
        self.__files: Optional[List[File]] = None
        self.__documents: Optional[List[Document]] = None
        self.__cluster_apis: ClusterAPIS = cluster_apis
        self.__dto_converter: FieldDtoConverter = dto_converter
        self.__data_types_catalog: Optional[FieldDataTypesCatalog] = None
        self.__well_properties_catalog: Optional[FieldWellPropertiesCatalog] = None
        self.__file_folders: Optional[List[FileFolder]] = None
        self.__pvts: Optional[List[PVT]] = None 
    def __get_field_dto(self) -> FieldDtoWithHierarchy:
        if self.__field_dto is None:
            self.__field_dto = self.__cluster_apis.field_api.get_field_dto(self.__id)
        return self.__field_dto
    @property
    def id(self) -> str:
        """ Gets the id of the :class:`Field` object.
        """
        return self.__id
    @property
    def name(self) -> str:
        """ Gets the name of the :class:`Field`.
        """
        return self.__name
    @property
    def asset(self) -> str:
        """ Gets the asset of the :class:`Field`.
        """
        return self.__asset
    @property
    def data_types_catalog(self) -> FieldDataTypesCatalog:
        """Get the Field Data Types catalog object of the :class:`Field`"""
        if self.__data_types_catalog is None:
            self.__data_types_catalog = self.__dto_converter.get_field_data_types_catalog_from_data_type_catalog_dto(self.__id, self.__get_field_dto().dataTypeCatalog)
        return self.__data_types_catalog
    @property
    def well_properties_catalog(self) -> FieldWellPropertiesCatalog:
        """Get the Field Well Properties catalog object of the :class:`Field`"""
        if self.__well_properties_catalog is None:
            self.__well_properties_catalog = self.__dto_converter.get_field_well_properties_catalog_from_well_property_catalog_dto(self.__id, self.__get_field_dto().wellPropertyCatalog)
        return self.__well_properties_catalog
    @property
    def reference_date(self) -> datetime:
        """ Gets the reference date of the :class:`Field`.
        """
        if self.__reference_date is None:
            self.__reference_date = self.__get_field_dto().referenceDate
        return self.__reference_date
    @property
    def wells(self) -> List[Well]:
        """ Gets the list of all wells contained in the :class:`Field`, including contained in the well groups.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        if self.__wells is None:
            self.__wells = self.__dto_converter.get_wells_by_field_from_field_dto(self.id, self.__get_field_dto(), self.data_types_catalog, self.well_properties_catalog)
        return self.__wells
    @property
    def well_groups(self) -> List[WellGroup]:
        """ Gets the list of all well groups contained in the :class:`Field`.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        if self.__well_groups is None:
            self.__well_groups = self.__dto_converter.get_well_groups_recursively(self.id, self.__get_field_dto(), self.data_types_catalog, self.well_properties_catalog)
        return self.__well_groups if self.__well_groups is not None else list()
    @property
    def files(self) -> List[File]:
        """ Gets the list of files contained in this :class:`Field` and its well groups.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        if self.__files is None:
            self.__files = self.__dto_converter.well_dto_converter.get_files_recursively(self.__id, None, self.__get_field_dto())
        return self.__files
    @property
    def file_folders(self) -> List[FileFolder]:
        """ Gets the list of file folders contained in this :class:`Well`.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        if self.__file_folders is None:
            self.__file_folders = self.__dto_converter.well_dto_converter.file_dto_converter.get_file_folders_from_file_folder_dto_recursively(self.__id, None, None, None, None, self.__get_field_dto().fileFolders)
        return self.__file_folders
    @property
    def documents(self) -> List[Document]:
        """ Gets the list of KW documents contained in this :class:`Field` and its well groups.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        document_list = list()
        for file in self.files:
            try:
                document = file.as_kw_document()
            except ValueError:
                document = None
            if document is not None:
                document_list.append(document)
        return document_list
[docs]
    def upload_file(self, file_path: str, file_folder_id: Optional[str] = None, overwrite: bool = False) -> File:
        """ Uploads a file to this :class:`Field`.
        Parameters
        ----------
        file_path:
            Full path and name of the file to upload.
        overwrite:
            A value indicating whether to overwrite a file with the same name if it already exists in the field.
        file_folder_id:
            Id of the file folder to upload the file
        Returns
        -------
        :class:`File`:
            An uploaded file object.
        """
        if file_folder_id is not None:
            file_folder = find_file_folder_recursively_by_id(file_folder_id, self.file_folders)
            if file_folder is None:
                raise ValueError(f"Missing File folder {file_folder_id} in field {self.__name}")
            file_dto = self.__cluster_apis.field_api.upload_file_to_file_folder_in_field(self.__id, file_folder.id, file_path, overwrite)
        else:
            file_dto = self.__cluster_apis.field_api.upload_file_to_field(self.__id, file_path, overwrite)
        file = self.__dto_converter.well_dto_converter.file_dto_converter.build_file_from_file_dto(None, file_dto)
        self.files.append(file)
        return file 
[docs]
    def refresh_data(self) -> None:
        """
        Clean all attributes and dto, to grab updated attributes.
        """
        self.__field_dto = None
        self.__data_types_catalog = None
        self.__well_properties_catalog = None
        self.__reference_date = None
        self.__wells = None
        self.__well_groups = None
        self.__files = None
        self.__pvts = None 
[docs]
    def create_well(self, name: str, uwi: Optional[str] = None, comment: Optional[str] = None, production_type: WellProductionTypeEnum = WellProductionTypeEnum.unknown, labels: Optional[List[str]] = None,
                    well_properties_values: Optional[List[Dict[str, Any]]] = None) -> Well:
        """
        Create a new well under the field associated to this :class:`Field`
        Parameters
        ----------
        name:
            Name of the new well
        uwi:
            Unique well identifier of the new well
        comment:
            Any description
        production_type:
            Production type of the new well, unknown by default
        labels:
            Labels of the new well
        well_properties_values:
            You can fill the well properties values, it has to be a dictionary following this format {'alias_of_the_well_property':value}
        Returns
        -------
        :class:`Well`:
            The new well
        """
        payload = self.__dto_converter.well_group_dto_converter.get_create_well_payload(name, uwi, comment, production_type, labels, well_properties_values)
        well = self.__dto_converter.well_group_dto_converter.build_well(self.id, None, self.__cluster_apis.field_api.create_well(self.__id, None, payload), self.data_types_catalog, self.well_properties_catalog)
        if self.__wells is not None:
            self.__wells.append(well)
        return well 
[docs]
    def rename(self, new_name: str) -> None:
        """ Rename the field
        Parameters
        ----------
        new_name:
            New name of the field
        """
        self.__cluster_apis.field_api.rename_field(self.__id, {"name": new_name})
        self.__name = new_name 
[docs]
    def delete(self) -> None:
        """ Delete the field"""
        self.__cluster_apis.field_api.delete_field(self.__id) 
[docs]
    def __str__(self) -> str:
        """String representation of the Field."""
        return f"Field(name='{self.name}', id='{self.id}')" 
[docs]
    def __repr__(self) -> str:
        """Detailed representation of the Field."""
        return f"Field(id='{self.id}', name='{self.name}', asset='{self.asset}')" 
    @property
    def pvts(self) -> List[PVT]:
        """ Gets the list of PVTs contained in this :class:`Field`.
        .. note:: This property is populated on-demand and is cached for the duration of the :class:`Connection`.
        """
        if self.__pvts is None:
            self.__pvts = self.__dto_converter.well_dto_converter.get_pvts_from_pvts_dto(self.__id, None, self.__get_field_dto().pvts)
        return self.__pvts
[docs]
    def create_pvt_from_file(self, pvt_name: str, file_id: str, start_date: Optional[datetime] = None,
                             reservoir_pressure: Optional[float] = None, reservoir_temperature: Optional[float] = None,
                             gas_oil_type: Optional[GasOilTypeEnum] = None, unit_system: Optional[UnitSystemPvtEnum] = None) -> PVT:
        """
        Creates a PVT (Pressure-Volume-Temperature) object from a file. You can define fallback parameters when the gas oil type is undetermined.
        Parameters
        ----------
        pvt_name : str
            The name of the PVT object to be created.
        file_id : str
            The identifier of the file from which the PVT object will be created.
        start_date : datetime, optional
            The start date for the PVT data coverage. Defaults to None.
        reservoir_pressure : float, optional
            The pressure of the reservoir associated with the PVT object. Defaults to None.
        reservoir_temperature : float, optional
            The temperature of the reservoir associated with the PVT object. Defaults to None.
        gas_oil_type : GasOilTypeEnum, optional
            The type of gas or oil associated with the PVT object, as per the enumerated
            GasOilTypeEnum. Defaults to None.
        unit_system : UnitSystemPvtEnum, optional
            The unit system used for the PVT object, as per the enumerated UnitSystemPvtEnum.
            Defaults to None.
        Returns
        -------
        PVT
            An instance of the PVT object created using the provided parameters and data from
            the specified text file.
        """
        dto = self.__dto_converter.well_dto_converter.get_command_pvt_from_text_file_dto(pvt_name, self.__id, None, file_id, start_date, reservoir_pressure, reservoir_temperature, gas_oil_type, unit_system)
        pvt = self.__dto_converter.well_dto_converter.build_pvt(self.__id, None, self.__cluster_apis.automation_api.create_pvt_from_text_file_field(self.id, dto))
        self.pvts.append(pvt)
        return pvt 
[docs]
    def create_pvt_from_kw_document(self, pvt_name: str, document_id: str, analysis_id: str) -> PVT:
        """
        Create a PVT object in the well group from a KW document
        Parameters
        ----------
        pvt_name: str
            Name of the PVT object to create
        document_id: str
            Id of the document to use
        analysis_id: str
            Id of the analysis to use
        Returns
        -------
        PVT
            The PVT object created.
        """
        dto = self.__dto_converter.well_dto_converter.get_command_pvt_from_kw_document_dto(pvt_name, self.__id, None, document_id, analysis_id)
        pvt = self.__dto_converter.well_dto_converter.build_pvt(self.__id, None, self.__cluster_apis.automation_api.create_pvt_from_kw_document_field(self.id, dto))
        self.pvts.append(pvt)
        return pvt 
[docs]
    def create_automatic_rtm_aggregator(self, pvt_document: Document, name: str = "Automatic RTM Agg #1", labels: Optional[List[str]] = None, use_black_oil_pvt: bool = False, pressure_data_type: str = "BHP", pressure_label: Optional[List[str]] = None, use_oil_rate: Optional[bool] = None, use_gas_rate: Optional[bool] = None, use_water_rate: Optional[bool] = None,
                                        wells: Optional[List[Well]] = None, power_law_segments: Literal[1, 2] = 1,
                                        artm_model: ARTMModelEnum = ARTMModelEnum.mzfd, improve_target: ARTMImproveTargetEnum = ARTMImproveTargetEnum.cumulative, regression_algorithm: ARTMRegressionAlgorithmEnum = ARTMRegressionAlgorithmEnum.genetic_algorithm, replicate_results_to_master_container: bool = False, use_equivalent_rate: bool = False,
                                        forecast_duration_hours: float = 2160, forecast_type: ARTMForecastTypeEnum = ARTMForecastTypeEnum.constant_pressure, forecast_value: Optional[float] = None, mandatory_well_properties: Optional[List[str]] = None) -> None:
        if wells is None:
            wells = self.wells
        if len(wells) == 0:
            raise ValueError("No well in the field to create the RTM aggregator")
        artm_parameters = pvt_document.get_artm_parameters()
        artm_creation_dto = self.__dto_converter.get_artm_aggretor_creation_dto(self.__id, name, [x.id for x in wells], pvt_document.file_id, artm_parameters, use_black_oil_pvt, pressure_data_type, pressure_label, use_oil_rate, use_gas_rate, use_water_rate, power_law_segments, artm_model, improve_target, regression_algorithm,
                                                                                replicate_results_to_master_container, use_equivalent_rate, forecast_duration_hours, forecast_type, forecast_value, mandatory_well_properties, labels)
        self.__cluster_apis.automation_api.create_automatic_rtm_aggregator_field(self.__id, artm_creation_dto)