"""
PriceExport (JSON → API) functionality for rateloader.

Implements Phase 4 of the rateloader project as specified in PRD §2.2.
Fetches JSON price data from Oracle and posts to HTTP API with exact
legacy compatibility for date format, headers, and iteration semantics.
"""

import json
import requests
import os
from datetime import datetime, timedelta
from dateutil.relativedelta import relativedelta
from typing import Optional, Dict, Any

from core.config import get_config
from core.oracle import create_oracle_wrapper
from core.logger import get_hotellabpriceexport_logger


class PriceExport:
    """
    PriceExport class handles fetching JSON price data from Oracle
    and posting it to the HTTP API with legacy compatibility.
    """

    def __init__(self):
        """Initialize PriceExport with configuration and logger."""
        self.config = get_config()
        self.logger = get_hotellabpriceexport_logger()
        self.oracle_wrapper = create_oracle_wrapper()

        # Get configuration values
        self.api_url = self.config.get("api_url")
        self.token = self.config.get("token")
        self.months = self.config.get_int("months", fallback=12)
        self.batch_max_size_kb = self.config.get_int("batch_max_size_kb", fallback=1024)  # 1MB default
        self.batch_max_days = self.config.get_int("batch_max_days", fallback=30)  # 30 days default
        self.deeper_logging = self.config.get_bool("deeper_logging", fallback=False)

        # Statistics tracking
        self.stats = {
            "total_days": 0,
            "successful_days": 0,
            "failed_days": 0,
            "oracle_errors": 0,
            "http_errors": 0,
            "json_errors": 0,
            "network_errors": 0,
            "batches_sent": 0,
            "batch_errors": 0,
        }

    def fetch_json_from_oracle(self, date_str: str) -> Optional[str]:
        """
        Fetch JSON price data from Oracle for a specific date.

        Uses the exact query format: "select m_hlpricejson('dd.mm.yyyy') from dual"

        Args:
            date_str: Date string in format 'dd.mm.yyyy'

        Returns:
            JSON string from Oracle or None if error occurs
        """
        try:
            # Build the exact query as specified in PRD
            query = f"select m_hlpricejson('{date_str}') from dual"
            self.logger.info(f"Fetching price data from Oracle for date: {date_str}")
            self.logger.debug(f"Executing Oracle query: {query}")

            # Execute scalar query to get JSON
            result = self.oracle_wrapper.execute_scalar(query)

            if result is None:
                self.logger.warning(
                    f"No price data returned from Oracle for date: {date_str}"
                )
                return None

            # Log data size for monitoring
            data_size = len(str(result))
            self.logger.info(f"Successfully fetched {data_size} bytes of price data for date: {date_str}")
            self.logger.debug(f"Raw data preview: {str(result)[:200]}...")
            return str(result)

        except Exception as e:
            self.logger.error(f"Oracle error fetching price data for date {date_str}: {str(e)}")
            self.stats["oracle_errors"] += 1
            return None

    def _save_http_data(self, request_data: Dict[str, Any], response_data: Dict[str, Any], timestamp: str) -> None:
        """
        Save HTTP request and response data to timestamped files when deeper logging is enabled.

        Args:
            request_data: Dictionary containing request details
            response_data: Dictionary containing response details
            timestamp: Timestamp string for filename
        """
        if not self.deeper_logging:
            return

        try:
            # Create log/price_export directory if it doesn't exist
            log_dir = os.path.join("log", "price_export")
            os.makedirs(log_dir, exist_ok=True)

            # Save request data
            request_file = os.path.join(log_dir, f"request_{timestamp}.json")
            with open(request_file, 'w', encoding='utf-8') as f:
                json.dump(request_data, f, indent=2, ensure_ascii=False)

            # Save response data
            response_file = os.path.join(log_dir, f"response_{timestamp}.json")
            with open(response_file, 'w', encoding='utf-8') as f:
                json.dump(response_data, f, indent=2, ensure_ascii=False)

            self.logger.info(f"Deeper logging: Saved HTTP data to {request_file} and {response_file}")

        except Exception as e:
            self.logger.error(f"Failed to save deeper logging data: {str(e)}")

    def post_json_to_api(self, json_data: str, date_str: str) -> bool:
        """
        Post JSON data to the HTTP API.

        Uses exact headers: {"Content-Type": "application/json", "X-HL-Token": token}

        Args:
            json_data: JSON string to post
            date_str: Date string for logging purposes

        Returns:
            True if successful, False otherwise
        """
        try:
            # Prepare exact headers as specified in PRD
            headers = {"Content-Type": "application/json", "X-HL-Token": self.token}

            self.logger.info(f"Posting price data to API for date: {date_str}")
            self.logger.debug(f"API URL: {self.api_url}")
            self.logger.debug(f"Headers: {headers}")
            self.logger.debug(f"Data size: {len(json_data)} bytes")

            # Prepare request data for deeper logging
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            request_data = {
                "timestamp": timestamp,
                "url": self.api_url,
                "method": "POST",
                "headers": headers,
                "data_length": len(json_data),
                "data": json_data
            }

            # Make HTTP POST request
            response = requests.post(
                self.api_url,
                data=json_data,
                headers=headers,
                timeout=30,  # 30 second timeout
            )

            # Prepare response data for deeper logging
            response_data = {
                "timestamp": timestamp,
                "status_code": response.status_code,
                "headers": dict(response.headers),
                "content_length": len(response.content),
                "content_preview": response.text[:500] + "..." if len(response.text) > 500 else response.text,
                "elapsed_seconds": response.elapsed.total_seconds()
            }

            # Save HTTP data if deeper logging is enabled
            self._save_http_data(request_data, response_data, timestamp)

            # Check response status
            if response.status_code == 200:
                self.logger.info(f"Successfully posted price data for date: {date_str} (Status: {response.status_code})")
                return True
            else:
                self.logger.error(
                    f"HTTP error posting price data for date {date_str}: "
                    f"Status {response.status_code}, Response: {response.text}"
                )
                self.stats["http_errors"] += 1
                return False

        except requests.exceptions.Timeout as e:
            self.logger.error(f"Timeout posting price data for date {date_str}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except requests.exceptions.ConnectionError as e:
            self.logger.error(f"Connection error posting price data for date {date_str}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except requests.exceptions.RequestException as e:
            self.logger.error(f"Network error posting price data for date {date_str}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except Exception as e:
            self.logger.error(f"Unexpected error posting price data for date {date_str}: {str(e)}")
            return False

    def post_batch_to_api(self, batch_json_data: str, batch_info: str) -> bool:
        """
        Post batched JSON data to the HTTP API.

        Uses the same format as single-day exports: raw JSON array with X-HL-Token header.
        The batch_json_data should be a concatenated JSON array from multiple dates.

        Args:
            batch_json_data: JSON string containing combined price data from multiple dates
            batch_info: Description of the batch for logging

        Returns:
            True if successful, False otherwise
        """
        try:
            # Use same headers as single-day exports
            headers = {"Content-Type": "application/json", "X-HL-Token": self.token}

            self.logger.info(f"Posting batched price data to API: {batch_info}")
            self.logger.debug(f"API URL: {self.api_url}")
            self.logger.debug(f"Headers: {headers}")
            self.logger.debug(f"Batch size: {len(batch_json_data)} bytes")

            # Prepare request data for deeper logging
            timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
            request_data = {
                "timestamp": timestamp,
                "url": self.api_url,
                "method": "POST",
                "headers": headers,
                "data_length": len(batch_json_data),
                "data": batch_json_data,
                "batch_info": batch_info
            }

            # Make HTTP POST request
            response = requests.post(
                self.api_url,
                data=batch_json_data,
                headers=headers,
                timeout=60,  # Longer timeout for batches
            )

            # Prepare response data for deeper logging
            response_data = {
                "timestamp": timestamp,
                "status_code": response.status_code,
                "headers": dict(response.headers),
                "content_length": len(response.content),
                "content_preview": response.text[:500] + "..." if len(response.text) > 500 else response.text,
                "elapsed_seconds": response.elapsed.total_seconds(),
                "batch_info": batch_info
            }

            # Save HTTP data if deeper logging is enabled
            self._save_http_data(request_data, response_data, timestamp)

            # Check response status
            if response.status_code == 200:
                self.logger.info(f"Successfully posted batched price data: {batch_info} (Status: {response.status_code})")
                self.stats["batches_sent"] += 1
                return True
            else:
                self.logger.error(
                    f"HTTP error posting batched price data {batch_info}: "
                    f"Status {response.status_code}, Response: {response.text}"
                )
                self.stats["batch_errors"] += 1
                return False

        except requests.exceptions.Timeout as e:
            self.logger.error(f"Timeout posting batched price data {batch_info}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except requests.exceptions.ConnectionError as e:
            self.logger.error(f"Connection error posting batched price data {batch_info}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except requests.exceptions.RequestException as e:
            self.logger.error(f"Network error posting batched price data {batch_info}: {str(e)}")
            self.stats["network_errors"] += 1
            return False
        except Exception as e:
            self.logger.error(f"Unexpected error posting batched price data {batch_info}: {str(e)}")
            return False

    def validate_json(self, json_data: str, date_str: str) -> bool:
        """
        Validate that the fetched data is valid JSON.

        Args:
            json_data: JSON string to validate
            date_str: Date string for logging purposes

        Returns:
            True if valid JSON, False otherwise
        """
        try:
            parsed_json = json.loads(json_data)
            self.logger.info(f"Valid JSON confirmed for date: {date_str} (contains {len(parsed_json)} items)")
            return True
        except json.JSONDecodeError as e:
            self.logger.error(f"Invalid JSON data for date {date_str}: {str(e)}")
            self.logger.debug(f"Problematic JSON data: {json_data[:500]}...")
            self.stats["json_errors"] += 1
            return False

    def export_prices_for_date(self, date_str: str) -> bool:
        """
        Export prices for a single date.

        Args:
            date_str: Date string in format 'dd.mm.yyyy'

        Returns:
            True if successful, False otherwise
        """
        self.logger.info(f"Starting price export process for date: {date_str}")

        try:
            # Fetch JSON from Oracle
            json_data = self.fetch_json_from_oracle(date_str)
            if json_data is None:
                self.logger.warning(f"Failed to fetch data for date: {date_str}")
                self.stats["failed_days"] += 1
                return False

            # Validate JSON
            if not self.validate_json(json_data, date_str):
                self.logger.warning(f"JSON validation failed for date: {date_str}")
                self.stats["failed_days"] += 1
                return False

            # Post to API
            if self.post_json_to_api(json_data, date_str):
                self.logger.info(f"Successfully completed price export for date: {date_str}")
                self.stats["successful_days"] += 1
                return True
            else:
                self.logger.warning(f"Failed to post data to API for date: {date_str}")
                self.stats["failed_days"] += 1
                return False

        except Exception as e:
            self.logger.error(f"Unexpected error processing price export for date {date_str}: {str(e)}")
            self.stats["failed_days"] += 1
            return False

    def export_price_range(self) -> Dict[str, Any]:
        """
        Export prices for the entire date range using batched requests.

        Accumulates data until reaching batch_max_size_kb (1MB) or batch_max_days (30),
        whichever comes first, then sends batched payload to API.

        Returns:
            Dictionary with export statistics
        """
        self.logger.info("Starting comprehensive price export process")
        self.logger.info(f"Configuration: months={self.months}, api_url={self.api_url}")
        self.logger.info(f"Batch settings: max_size_kb={self.batch_max_size_kb}, max_days={self.batch_max_days}")

        # Calculate date range
        start_date = datetime.now()
        end_date = start_date + relativedelta(months=+self.months)
        oneday = timedelta(days=1)

        total_days = int((end_date - start_date).days) + 1
        self.logger.info(
            f"Exporting prices for {total_days} days from {start_date.strftime('%d.%m.%Y')} "
            f"to {end_date.strftime('%d.%m.%Y')} (months: {self.months})"
        )

        # Connect to Oracle
        if not self.oracle_wrapper.connect():
            self.logger.error("Failed to connect to Oracle database - aborting price export")
            return self.stats

        try:
            # Initialize batch accumulation
            current_batch_data = []  # List to accumulate raw JSON arrays
            batch_start_date = start_date
            batch_size_bytes = 0
            batch_day_count = 0

            # Legacy iteration pattern: while start_date <= end_date: … start_date += oneday
            current_date = start_date
            processed_count = 0

            while current_date <= end_date:
                date_str = current_date.strftime("%d.%m.%Y")
                self.stats["total_days"] += 1
                processed_count += 1
                batch_day_count += 1

                # Log progress every 10 days or at start/end
                if processed_count % 10 == 1 or processed_count == total_days:
                    self.logger.info(f"Processing day {processed_count}/{total_days}: {date_str}")

                # Fetch data for this date
                json_data = self.fetch_json_from_oracle(date_str)
                if json_data is None:
                    self.logger.warning(f"Failed to fetch data for date: {date_str}")
                    self.stats["failed_days"] += 1
                else:
                    # Validate JSON
                    if not self.validate_json(json_data, date_str):
                        self.logger.warning(f"JSON validation failed for date: {date_str}")
                        self.stats["failed_days"] += 1
                    else:
                        # Add to batch - parse and extend the array
                        try:
                            parsed_data = json.loads(json_data)
                            current_batch_data.extend(parsed_data)  # Extend with the array contents
                            # Recalculate total batch size after adding this date's data
                            batch_json_preview = json.dumps(current_batch_data, ensure_ascii=False)
                            batch_size_bytes = len(batch_json_preview.encode('utf-8'))
                            self.stats["successful_days"] += 1
                            self.logger.debug(f"Added data to batch for date: {date_str} (batch size now: {batch_size_bytes} bytes)")
                        except Exception as e:
                            self.logger.error(f"Failed to add data to batch for date {date_str}: {str(e)}")
                            self.stats["failed_days"] += 1

                # Check if batch should be sent (size limit reached or last date)
                # Removed day limit to allow larger batches that reach size limit
                should_send_batch = (
                    (current_batch_data and batch_size_bytes >= (self.batch_max_size_kb * 1024)) or  # Size limit (only if we have data)
                    current_date == end_date                               # Last date
                )

                if should_send_batch and current_batch_data:
                    # Convert accumulated data back to JSON
                    batch_json = json.dumps(current_batch_data, ensure_ascii=False)

                    # Send the batch
                    batch_info = f"{batch_day_count} dates ({batch_start_date.strftime('%d.%m.%Y')} to {current_date.strftime('%d.%m.%Y')})"
                    self.logger.info(f"Sending batch: {batch_info}, size: {batch_size_bytes} bytes")

                    if self.post_batch_to_api(batch_json, batch_info):
                        self.logger.info(f"Successfully sent batch: {batch_info}")
                    else:
                        self.logger.error(f"Failed to send batch: {batch_info}")
                        # Mark all days in this batch as failed for statistics
                        # (Note: This is a simplification; in production you might want more granular tracking)

                    # Reset batch
                    current_batch_data = []
                    batch_start_date = current_date + oneday
                    batch_size_bytes = 0
                    batch_day_count = 0

                # Move to next day
                current_date += oneday

            # Send any remaining batch
            if current_batch_data:
                batch_json = json.dumps(current_batch_data, ensure_ascii=False)
                batch_info = f"{batch_day_count} dates ({batch_start_date.strftime('%d.%m.%Y')} to {current_date.strftime('%d.%m.%Y')})"
                self.logger.info(f"Sending final batch: {batch_info}, size: {batch_size_bytes} bytes")

                if self.post_batch_to_api(batch_json, batch_info):
                    self.logger.info(f"Successfully sent final batch: {batch_info}")
                else:
                    self.logger.error(f"Failed to send final batch: {batch_info}")

        except Exception as e:
            self.logger.error(f"Unexpected error during price export range processing: {str(e)}")
        finally:
            # Always close Oracle connection
            self.oracle_wrapper.disconnect()
            self.logger.info("Oracle database connection closed")

        # Log comprehensive final statistics
        success_rate = (self.stats["successful_days"] / self.stats["total_days"] * 100) if self.stats["total_days"] > 0 else 0

        self.logger.info("=" * 60)
        self.logger.info("PRICE EXPORT PROCESS COMPLETED")
        self.logger.info("=" * 60)
        self.logger.info(f"Total days processed: {self.stats['total_days']}")
        self.logger.info(f"Successful exports: {self.stats['successful_days']}")
        self.logger.info(f"Failed exports: {self.stats['failed_days']}")
        self.logger.info(f"Batches sent: {self.stats['batches_sent']}")
        self.logger.info(f"Batch errors: {self.stats['batch_errors']}")
        self.logger.info(f"Success rate: {success_rate:.1f}%")
        self.logger.info(f"Error breakdown:")
        self.logger.info(f"  - Oracle errors: {self.stats['oracle_errors']}")
        self.logger.info(f"  - HTTP errors: {self.stats['http_errors']}")
        self.logger.info(f"  - JSON validation errors: {self.stats['json_errors']}")
        self.logger.info(f"  - Network errors: {self.stats['network_errors']}")
        self.logger.info(f"  - Batch errors: {self.stats['batch_errors']}")
        self.logger.info("=" * 60)

        return self.stats

    def reset_stats(self) -> None:
        """Reset statistics counters."""
        self.stats = {
            "total_days": 0,
            "successful_days": 0,
            "failed_days": 0,
            "oracle_errors": 0,
            "http_errors": 0,
            "json_errors": 0,
            "network_errors": 0,
            "batches_sent": 0,
            "batch_errors": 0,
        }

    def cleanup(self) -> None:
        """Clean up resources used by PriceExport."""
        try:
            if self.oracle_wrapper:
                self.oracle_wrapper.disconnect()
                self.logger.debug("PriceExport Oracle connection cleaned up")
        except Exception as e:
            self.logger.error(f"Error during PriceExport cleanup: {str(e)}")


def main() -> Dict[str, Any]:
    """
    Main function that orchestrates the entire price export process.

    Returns:
        Dictionary with export statistics
    """
    logger = get_hotellabpriceexport_logger()
    logger.info("=" * 60)
    logger.info("PRICE EXPORT PROCESS STARTED")
    logger.info("=" * 60)

    try:
        # Create PriceExport instance
        price_export = PriceExport()
        logger.info("PriceExport instance initialized")

        # Check configuration
        if not price_export.api_url:
            logger.error("CRITICAL: API URL not configured in settings.ini")
            return {"error": "API URL not configured"}

        if not price_export.token:
            logger.error("CRITICAL: API token not configured in settings.ini")
            return {"error": "API token not configured"}

        logger.info("Configuration validated successfully")
        logger.info(f"API URL: {price_export.api_url}")
        logger.info(f"Export months: {price_export.months}")

        # Run the export process
        stats = price_export.export_price_range()

        logger.info("PriceExport process completed successfully")
        return stats

    except Exception as e:
        logger.error(f"CRITICAL: PriceExport process failed with exception: {str(e)}", exc_info=True)
        return {"error": str(e)}
    finally:
        logger.info("=" * 60)


if __name__ == "__main__":
    # Run the export process
    result = main()

    # Print summary
    if "error" in result:
        print(f"PriceExport failed: {result['error']}")
    else:
        print(f"PriceExport completed:")
        print(f"  Total days: {result['total_days']}")
        print(f"  Successful: {result['successful_days']}")
        print(f"  Failed: {result['failed_days']}")
