from __future__ import annotations

import hashlib
import json
import logging
import time
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional

import boto3
from botocore.config import Config as BotoConfig

from .config import AwsConfig

logger = logging.getLogger(__name__)


class S3Storage:
    def __init__(self, cfg: AwsConfig, session: Optional[boto3.session.Session] = None, no_aws: bool = False) -> None:
        self.cfg = cfg
        self.no_aws = no_aws
        if not no_aws:
            sess = session or boto3.session.Session(region_name=cfg.region)
            self.s3 = sess.resource("s3", config=BotoConfig(retries={"max_attempts": 5}))
        else:
            self.s3 = None

    def _timestamp_prefix(self) -> str:
        now = datetime.now(timezone.utc)
        return now.strftime("%Y/%m/%d")
    
    def _get_website_name(self, url: str) -> str:
        """Extract website name from URL for file naming"""
        from urllib.parse import urlparse
        try:
            parsed = urlparse(url)
            domain = parsed.netloc.lower()
            # Remove www. prefix and common suffixes
            domain = domain.replace('www.', '')
            # Replace dots with underscores for valid filename
            return domain.replace('.', '_')
        except Exception:
            return "unknown_site"

    def _hash_key(self, url_or_id: str) -> str:
        return hashlib.sha256(url_or_id.encode("utf-8")).hexdigest()[:16]


    def put_json(self, bucket_name: str, key: str, payload: Dict[str, Any]) -> str:
        """Store JSON data with specified bucket and key"""
        body = json.dumps(payload, ensure_ascii=False).encode("utf-8")
        if self.no_aws:
            logger.info("no_aws mode: skipping S3 JSON upload for %s -> %s/%s", bucket_name, bucket_name, key)
            return f"s3://{bucket_name}/{key}"
        obj = self.s3.Object(bucket_name, key)
        obj.put(
            Body=body,
            ContentType="application/json",
            Tagging=f"resource-tag={self.cfg.resource_tag}",
        )
        return f"s3://{bucket_name}/{key}"

    def put_json_with_url(self, url: str, payload: Dict[str, Any]) -> str:
        """Store JSON data with website name as filename in month/day structure"""
        website_name = self._get_website_name(url)
        timestamp_prefix = self._timestamp_prefix()
        key = f"{timestamp_prefix}/{website_name}_{self._hash_key(url)}.json"
        return self.put_json(self.cfg.s3_bucket_processed, key, payload)

    def list_objects(self, bucket_name: str, prefix: str = "") -> list[str]:
        """List all objects in the specified bucket with optional prefix"""
        if self.no_aws:
            logger.info("no_aws mode: returning empty list for bucket %s", bucket_name)
            return []
        
        try:
            bucket = self.s3.Bucket(bucket_name)
            objects = []
            for obj in bucket.objects.filter(Prefix=prefix):
                objects.append(obj.key)
            return objects
        except Exception as e:
            logger.error("Failed to list objects in bucket %s: %s", bucket_name, e)
            return []

    def get_json(self, bucket_name: str, key: str) -> Optional[Dict[str, Any]]:
        """Get and parse JSON object from S3"""
        if self.no_aws:
            logger.info("no_aws mode: returning None for %s/%s", bucket_name, key)
            return None
        
        try:
            obj = self.s3.Object(bucket_name, key)
            response = obj.get()
            content = response['Body'].read().decode('utf-8')
            return json.loads(content)
        except Exception as e:
            logger.error("Failed to get JSON from %s/%s: %s", bucket_name, key, e)
            return None

    def get_object_metadata(self, bucket_name: str, key: str) -> Dict[str, Any]:
        """Get object metadata from S3"""
        if self.no_aws:
            logger.info("no_aws mode: returning mock metadata for %s/%s", bucket_name, key)
            return {"LastModified": datetime.now(timezone.utc)}
        
        try:
            obj = self.s3.Object(bucket_name, key)
            response = obj.get()
            return {
                "LastModified": response.get('LastModified', datetime.now(timezone.utc)),
                "Size": response.get('ContentLength', 0),
                "ETag": response.get('ETag', ''),
                "ContentType": response.get('ContentType', '')
            }
        except Exception as e:
            logger.error("Failed to get metadata for %s/%s: %s", bucket_name, key, e)
            return {"LastModified": datetime.now(timezone.utc)}

    def check_website_exists(self, url: str) -> bool:
        """Check if data for a specific URL already exists in S3 (within last 30 days)"""
        if self.no_aws:
            logger.info("no_aws mode: returning False for website existence check")
            return False
        
        try:
            from datetime import timedelta
            
            website_name = self._get_website_name(url)
            url_hash = self._hash_key(url)
            
            # Search for the specific URL hash, not just any file from the website
            bucket = self.s3.Bucket(self.cfg.s3_bucket_processed)
            
            # Look for files matching the exact URL hash pattern
            # File format: YYYY/MM/DD/{website_name}_{url_hash}.json
            expected_filename = f"{website_name}_{url_hash}.json"
            
            # Only check the last 30 days to avoid scanning the entire bucket
            # This prevents old data from blocking new articles
            now = datetime.now(timezone.utc)
            for days_back in range(30):
                date = now - timedelta(days=days_back)
                date_prefix = date.strftime("%Y/%m/%d")
                search_key = f"{date_prefix}/{expected_filename}"
                
                # Check if this specific file exists
                try:
                    obj = self.s3.Object(self.cfg.s3_bucket_processed, search_key)
                    obj.load()  # This will raise an exception if the object doesn't exist
                    logger.info("Found existing data for URL %s: %s", url, search_key)
                    return True
                except:
                    # Object doesn't exist for this date, continue checking other dates
                    continue
            
            logger.debug("No existing data found for URL %s (hash: %s) in last 30 days", url, url_hash)
            return False
            
        except Exception as e:
            logger.error("Error checking website existence for %s: %s", url, e)
            return False

    def get_website_files(self, url: str) -> List[str]:
        """Get all existing files for a website"""
        if self.no_aws:
            logger.info("no_aws mode: returning empty list for website files")
            return []
        
        try:
            website_name = self._get_website_name(url)
            bucket = self.s3.Bucket(self.cfg.s3_bucket_processed)
            
            files = []
            for obj in bucket.objects.all():
                if website_name in obj.key and obj.key.endswith('.json'):
                    files.append(obj.key)
            
            logger.debug("Found %d existing files for website %s", len(files), website_name)
            return files
            
        except Exception as e:
            logger.error("Error getting website files for %s: %s", url, e)
            return []

    def ensure_buckets_with_lifecycle(self) -> None:
        if self.no_aws:
            logger.info("no_aws mode: skipping bucket ensure")
            return
        s3_client = self.s3.meta.client
        try:
            s3_client.head_bucket(Bucket=self.cfg.s3_bucket_processed)
        except Exception:
            logger.info("creating bucket %s", self.cfg.s3_bucket_processed)
            create_kwargs = {"Bucket": self.cfg.s3_bucket_processed}
            if self.cfg.region != "us-east-1":
                create_kwargs["CreateBucketConfiguration"] = {"LocationConstraint": self.cfg.region}
            s3_client.create_bucket(**create_kwargs)
            s3_client.put_bucket_tagging(
                Bucket=self.cfg.s3_bucket_processed,
                Tagging={"TagSet": [{"Key": "resource-tag", "Value": self.cfg.resource_tag}]},
            )
        # Put lifecycle
        rules = {
            "Rules": [
                {
                    "ID": "expire-old-objects",
                    "Prefix": "",
                    "Status": "Enabled",
                    "Expiration": {"Days": self.cfg.lifecycle_days},
                }
            ]
        }
        try:
            s3_client.put_bucket_lifecycle_configuration(Bucket=self.cfg.s3_bucket_processed, LifecycleConfiguration=rules)
        except Exception as e:
            logger.warning("failed to set lifecycle on %s: %s", self.cfg.s3_bucket_processed, e)


