from collections import OrderedDict
from pathlib import Path
from typing import Dict, List, Optional

from phi.cli.console import print_heading, print_info
from phi.cli.settings import phi_cli_settings
from phi.api.schemas.user import UserSchema
from phi.api.schemas.workspace import WorkspaceSchema, WorkspaceDelete
from phi.utils.log import logger
from phi.utils.json_io import read_json_file, write_json_file
from phi.workspace.config import WorkspaceConfig


class PhiCliConfig:
    """The PhiCliConfig class manages user data for the phi cli"""

    def __init__(
        self,
        user: Optional[UserSchema] = None,
        active_ws_dir: Optional[str] = None,
        ws_config_map: Optional[Dict[str, WorkspaceConfig]] = None,
    ) -> None:
        # Current user, populated after authenticating with the api
        # To add a user, use the user setter
        self._user: Optional[UserSchema] = user

        # Active ws dir - used as the default for `phi` commands
        # To add an active workspace, use the active_ws_dir setter
        self._active_ws_dir: Optional[str] = active_ws_dir

        # Mapping from ws_root_path to ws_config
        self.ws_config_map: Dict[str, WorkspaceConfig] = ws_config_map or OrderedDict()

    ######################################################
    ## User functions
    ######################################################

    @property
    def user(self) -> Optional[UserSchema]:
        return self._user

    @user.setter
    def user(self, user: Optional[UserSchema]) -> None:
        """Sets the user"""
        if user is not None:
            logger.debug(f"Setting user to: {user.email}")
            clear_user_cache = (
                self._user is not None  # previous user is not None
                and self._user.email != "anon"  # previous user is not anon
                and (user.email != self._user.email or user.id_user != self._user.id_user)  # new user is different
            )
            self._user = user
            if clear_user_cache:
                self.clear_user_cache()
            self.save_config()

    def clear_user_cache(self) -> None:
        """Clears the user cache"""
        logger.debug("Clearing user cache")
        self.ws_config_map.clear()
        self._active_ws_dir = None
        phi_cli_settings.ai_conversations_path.unlink(missing_ok=True)
        logger.info("Workspaces cleared, please setup again using `phi ws setup`")

    ######################################################
    ## Workspace functions
    ######################################################

    @property
    def active_ws_dir(self) -> Optional[str]:
        return self._active_ws_dir

    def set_active_ws_dir(self, ws_root_path: Optional[Path]) -> None:
        if ws_root_path is not None:
            logger.debug(f"Setting active workspace to: {str(ws_root_path)}")
            self._active_ws_dir = str(ws_root_path)
            self.save_config()

    @property
    def available_ws(self) -> List[WorkspaceConfig]:
        return list(self.ws_config_map.values())

    def _add_or_update_ws_config(
        self, ws_root_path: Path, ws_schema: Optional[WorkspaceSchema] = None
    ) -> Optional[WorkspaceConfig]:
        """The main function to create, update or refresh a WorkspaceConfig.

        This function does not call self.save_config(). Remember to save_config() after calling this function.
        """

        # Validate ws_root_path
        if ws_root_path is None or not isinstance(ws_root_path, Path):
            raise ValueError(f"Invalid ws_root: {ws_root_path}")
        ws_root_str = str(ws_root_path)

        ######################################################
        # Create new ws_config if one does not exist
        ######################################################
        if ws_root_str not in self.ws_config_map:
            logger.debug(f"Creating workspace at: {ws_root_str}")
            new_workspace_config = WorkspaceConfig(
                ws_root_path=ws_root_path,
                ws_schema=ws_schema,
            )
            self.ws_config_map[ws_root_str] = new_workspace_config
            logger.debug(f"Workspace created at: {ws_root_str}")

            # Return the new_workspace_config
            return new_workspace_config

        ######################################################
        # Update ws_config
        ######################################################
        logger.debug(f"Updating workspace at: {ws_root_str}")
        # By this point there should be a WorkspaceConfig object for this ws_name
        existing_ws_config: Optional[WorkspaceConfig] = self.ws_config_map.get(ws_root_str, None)
        if existing_ws_config is None:
            logger.error(f"Could not find workspace at: {ws_root_str}, please run `phi ws setup`")
            return None

        # Update the ws_schema if it's not None and different from the existing one
        if ws_schema is not None and existing_ws_config.ws_schema != ws_schema:
            existing_ws_config.ws_schema = ws_schema
        logger.debug(f"Workspace updated: {ws_root_str}")

        # Return the updated_ws_config
        return existing_ws_config

    ######################################################
    # END
    ######################################################

    def add_new_ws_to_config(self, ws_root_path: Path) -> Optional[WorkspaceConfig]:
        """Adds a newly created workspace to the PhiCliConfig"""

        ws_config = self._add_or_update_ws_config(ws_root_path=ws_root_path)
        self.save_config()
        return ws_config

    def update_ws_config(
        self,
        ws_root_path: Path,
        ws_schema: Optional[WorkspaceSchema] = None,
        set_as_active: bool = False,
    ) -> Optional[WorkspaceConfig]:
        """Updates WorkspaceConfig and returns True if successful"""
        ws_config = self._add_or_update_ws_config(
            ws_root_path=ws_root_path,
            ws_schema=ws_schema,
        )
        if set_as_active:
            self._active_ws_dir = str(ws_root_path)
        self.save_config()
        return ws_config

    def delete_ws(self, ws_root_path: Path) -> None:
        """Handles Deleting a workspace from the PhiCliConfig and api"""

        ws_root_str = str(ws_root_path)
        print_heading(f"Deleting record for workspace at: {ws_root_str}")
        print_info("-*- Note: this does not delete any files on disk, please delete them manually")

        ws_config: Optional[WorkspaceConfig] = self.ws_config_map.pop(ws_root_str, None)
        if ws_config is None:
            logger.warning(f"No record of workspace at {ws_root_str}")
            return

        # Check if we're deleting the active workspace, if yes, unset the active ws
        if self._active_ws_dir is not None and self._active_ws_dir == ws_root_str:
            print_info(f"Removing {ws_root_str} as the active workspace")
            self._active_ws_dir = None

        if self.user is not None and ws_config.ws_schema is not None:
            print_info("Deleting workspace from the server")

            from phi.api.workspace import delete_workspace_for_user

            delete_workspace_for_user(
                user=self.user,
                workspace=WorkspaceDelete(
                    id_workspace=ws_config.ws_schema.id_workspace, ws_name=ws_config.ws_schema.ws_name
                ),
            )
        self.save_config()

    ######################################################
    ## Get Workspace Data
    ######################################################

    def get_ws_config_by_dir_name(self, ws_dir_name: str) -> Optional[WorkspaceConfig]:
        ws_root_str: Optional[str] = None
        for k, v in self.ws_config_map.items():
            if v.ws_root_path.stem == ws_dir_name:
                ws_root_str = k
                break

        if ws_root_str is None or ws_root_str not in self.ws_config_map:
            return None

        return self.ws_config_map[ws_root_str]

    def get_ws_config_by_path(self, ws_root_path: Path) -> Optional[WorkspaceConfig]:
        return self.ws_config_map[str(ws_root_path)] if str(ws_root_path) in self.ws_config_map else None

    def get_active_ws_config(self) -> Optional[WorkspaceConfig]:
        if self.active_ws_dir is not None and self.active_ws_dir in self.ws_config_map:
            return self.ws_config_map[self.active_ws_dir]
        return None

    ######################################################
    ## Save PhiCliConfig
    ######################################################

    def save_config(self):
        config_data = {
            "user": self.user.model_dump() if self.user else None,
            "active_ws_dir": self.active_ws_dir,
            "ws_config_map": {k: v.to_dict() for k, v in self.ws_config_map.items()},
        }
        write_json_file(file_path=phi_cli_settings.config_file_path, data=config_data)

    @classmethod
    def from_saved_config(cls):
        try:
            config_data = read_json_file(file_path=phi_cli_settings.config_file_path)
            if config_data is None or not isinstance(config_data, dict):
                logger.debug("No config found")
                return cls()

            user_dict = config_data.get("user")
            user_schema = UserSchema.model_validate(user_dict) if user_dict else None
            active_ws_dir = config_data.get("active_ws_dir")

            # Create a new config
            new_config = cls(user_schema, active_ws_dir)

            # Add all the workspaces
            for k, v in config_data.get("ws_config_map", {}).items():
                _ws_config = WorkspaceConfig.from_dict(v)
                if _ws_config is not None:
                    new_config.ws_config_map[k] = _ws_config
            return new_config
        except Exception as e:
            logger.warning(e)
            logger.warning("Please setup the workspace using `phi ws setup`")

    ######################################################
    ## Print PhiCliConfig
    ######################################################

    def print_to_cli(self, show_all: bool = False):
        if self.user:
            print_heading(f"User: {self.user.email}\n")
        if self.active_ws_dir:
            print_heading(f"Active workspace directory: {self.active_ws_dir}\n")
        else:
            print_info("No active workspace found.")
            print_info(
                "Please create a workspace using `phi ws create` " "or setup existing workspace using `phi ws setup`"
            )

        if show_all and len(self.ws_config_map) > 0:
            print_heading("Available workspaces:\n")
            c = 1
            for k, v in self.ws_config_map.items():
                print_info(f"  {c}. {k}")
                if v.ws_schema and v.ws_schema.ws_name:
                    print_info(f"     Name: {v.ws_schema.ws_name}")
                c += 1
