from google.cloud import storage
from google.cloud.exceptions import NotFound
from typing import Optional, Dict, List
from datetime import timedelta
from .base import BaseStorageProvider
from .exceptions import StorageUploadError, StorageDownloadError, StorageNotFoundError
from urllib.parse import urlparse
import base64

class GCPStorage(BaseStorageProvider):
    """Google Cloud Storage implementation"""

    def _initialize_client(self):
        """Initialize GCP Storage client"""
        if 'credentials_path' in self.config:
            self.client = storage.Client.from_service_account_json(
                self.config['credentials_path']
            )
        else:
            self.client = storage.Client()

        self.bucket = self.client.bucket(self.bucket_name)

    def _normalize_key3(self, remote_path: str) -> str:
        """
        Normalize path to S3 object key
        Supports:
        - key
        - bucket/key
        - full URL
        """

        # If full URL
        if remote_path.startswith("http://") or remote_path.startswith("https://"):
            parsed = urlparse(remote_path)
            key = parsed.path.lstrip("/")
        else:
            key = remote_path

        # Remove bucket prefix if present
        if key.startswith(self.bucket_name + "/"):
            key = key[len(self.bucket_name) + 1 :]

        return key

    def _normalize_key(self, remote_path: str) -> str:
        """
        Normalize path to S3 object key
        """

        key = remote_path

        # If URL
        if remote_path.startswith(("http://", "https://")):
            parsed = urlparse(remote_path)

            # If this is our bucket domain → remove it
            if self.bucket_name in parsed.netloc:
                key = parsed.path.lstrip("/")
            else:
                # keep domain + path
                key = f"{parsed.netloc}{parsed.path}"

        # Remove bucket prefix if present
        if key.startswith(f"{self.bucket_name}/"):
            key = key[len(self.bucket_name) + 1 :]

        if key.startswith(f"{self.bucket_name}/"):
            key = key[len(self.bucket_name) + 1 :]

        if "hosted_domain" in self.config:
            if self.config["hosted_domain"] != "":
                if key.startswith(f"{self.config['hosted_domain']}/"):
                    key = key[len(self.config["hosted_domain"]) + 1 :]

        return key

    def copy(self, source_path: str, destination_path: str) -> str:
        """Copy object inside GCS bucket"""
        try:
            source_key = self._normalize_key(source_path)
            destination_key = self._normalize_key(destination_path)

            source_blob = self.bucket.blob(source_key)

            new_blob = self.bucket.copy_blob(
                source_blob,
                self.bucket,
                destination_key
            )

            return new_blob.public_url

        except NotFound:
            raise StorageNotFoundError(f"Object not found: {source_path}")
        except Exception as e:
            raise StorageUploadError(f"Failed to copy object in GCS: {str(e)}")

    


    def download_as_string(self, remote_path: str):
        """Download GCS object and return string (text or base64 for binary)"""
        try:
            key = self._normalize_key(remote_path)

            blob = self.bucket.blob(key)

            data = blob.download_as_bytes()
            content_type = blob.content_type or ""

            # text files
            if (
                "text" in content_type
                or "json" in content_type
                or "xml" in content_type
                or "javascript" in content_type
                or "svg" in content_type
            ):
                return data.decode("utf-8"), content_type

            # binary files -> convert to base64 string
            encoded = base64.b64encode(data).decode("utf-8")
            base64_data = f"data:{content_type};base64,{encoded}"

            return base64_data, content_type

        except Exception as e:
            raise Exception(f"Failed to download object: {e}")
        
    def download_as_bytes(self, remote_path: str) -> bytes:
        """Download GCS object and return bytes"""
        try:
            key = self._normalize_key(remote_path)

            blob = self.bucket.blob(key)

            return blob.download_as_bytes()

        except Exception as e:
            raise Exception(f"Failed to download object: {e}")

    def get_content_type(self, remote_path: str) -> str:
        """Get content type of GCS object"""
        try:
            key = self._normalize_key(remote_path)

            blob = self.bucket.get_blob(key)

            if not blob:
                raise Exception("Object not found")

            return blob.content_type

        except Exception as e:
            raise Exception(f"Failed to get content type: {e}")

    def exists(self, remote_path: str) -> bool:
        """Check if GCP object exists"""
        try:
            key = self._normalize_key(remote_path)

            bucket = self.client.bucket(self.bucket_name)
            blob = bucket.blob(key)

            return blob.exists()

        except Exception as e:
            raise Exception(f"Failed to check object existence: {e}")
    def get_size(self, remote_path: str) -> int:
        """Get object size in bytes"""
        try:
            key = self._normalize_key(remote_path)

            response = self.client.head_object(Bucket=self.bucket_name, Key=key)

            return response["ContentLength"]

        except Exception as e:
            raise Exception(f"Failed to get object size: {str(e)}")

    def upload(self, data: bytes, remote_path: str,
               content_type: str = "application/octet-stream",
               metadata: Optional[Dict[str, str]] = None,
               cache_control: Optional[str] = None) -> str:
        """Upload data to GCS"""
        try:
            blob = self.bucket.blob(remote_path)
            blob.content_type = content_type

            if metadata:
                blob.metadata = metadata
            if cache_control:
                blob.cache_control = cache_control

            blob.upload_from_string(data, content_type=content_type)

            return self.get_public_url(remote_path)

        except Exception as e:
            raise StorageUploadError(f"Failed to upload to GCS: {str(e)}")

    def download(self, remote_path: str) -> bytes:
        """Download data from GCS"""
        try:
            blob = self.bucket.blob(remote_path)
            return blob.download_as_bytes()

        except NotFound:
            raise StorageNotFoundError(f"Object not found: {remote_path}")
        except Exception as e:
            raise StorageDownloadError(f"Failed to download from GCS: {str(e)}")

    def delete(self, remote_path: str) -> bool:
        """Delete object from GCS"""
        try:
            blob = self.bucket.blob(remote_path)
            blob.delete()
            return True
        except Exception as e:
            raise StorageUploadError(f"Failed to delete from GCS: {str(e)}")

    def list_objects(self, prefix: str = "", max_results: int = 1000) -> List[str]:
        """List objects in GCS bucket"""
        try:
            blobs = self.client.list_blobs(
                self.bucket_name,
                prefix=prefix,
                max_results=max_results
            )
            return [blob.name for blob in blobs]

        except Exception as e:
            raise StorageUploadError(f"Failed to list GCS objects: {str(e)}")

    def exists(self, remote_path: str) -> bool:
        """Check if object exists in GCS"""
        blob = self.bucket.blob(remote_path)
        return blob.exists()

    def get_public_url(self, remote_path: str) -> str:
        """Get public URL for GCS object"""
        blob = self.bucket.blob(remote_path)
        return blob.public_url

    def get_signed_url(self, remote_path: str, expiration: int = 3600) -> str:
        """Generate signed URL for GCS object"""
        try:
            blob = self.bucket.blob(remote_path)
            url = blob.generate_signed_url(
                expiration=timedelta(seconds=expiration),
                method='GET'
            )
            return url
        except Exception as e:
            raise StorageUploadError(f"Failed to generate signed URL: {str(e)}")

    def rename(self, source_path: str, destination_path: str) -> str:
        """Rename object in GCP bucket"""
        try:
            source_key = self._normalize_key(source_path)
            destination_key = self._normalize_key(destination_path)

            bucket = self.client.bucket(self.bucket_name)

            blob = bucket.blob(source_key)

            new_blob = bucket.rename_blob(blob, destination_key)

            return new_blob.public_url

        except NotFound:
            raise Exception(f"Object not found: {source_path}")
        except Exception as e:
            raise Exception(f"Failed to rename GCP object: {str(e)}")
