"""
Formations module for chart formation management.

Handles CRUD operations for chart formations (trendlines, channels, patterns)
with database-backed storage via DataCache.
"""
import json
import logging
import uuid
import time
from dataclasses import dataclass, field
from typing import Any, Dict, List, Optional

from DataCache_v3 import DataCache

# Configure logging
logger = logging.getLogger(__name__)


@dataclass
class Formation:
    """Class for individual formation properties."""
    tbl_key: str
    user_id: int
    name: str
    formation_type: str  # 'support_resistance', 'channel', etc.
    exchange: str
    market: str
    timeframe: str
    lines_json: str  # JSON string of line data
    color: str = '#667eea'
    visible: bool = True
    created_at: int = 0
    updated_at: int = 0

    @property
    def lines(self) -> List[dict]:
        """Parse lines_json and return as list of line dicts."""
        try:
            data = json.loads(self.lines_json)
            return data.get('lines', [])
        except (json.JSONDecodeError, TypeError):
            return []

    @property
    def targets(self) -> List[dict]:
        """Parse lines_json and return targets (Phase C feature)."""
        try:
            data = json.loads(self.lines_json)
            return data.get('targets', [])
        except (json.JSONDecodeError, TypeError):
            return []


class Formations:
    """Manages chart formations with database-backed storage via DataCache."""

    TABLE_NAME = 'formations'

    def __init__(self, data_cache: DataCache):
        """
        Initialize the Formations class.

        :param data_cache: Instance of DataCache to manage cache and database interactions.
        """
        self.data_cache = data_cache

        # Ensure the formations table exists in the database
        self._ensure_table_exists()

        # Create a cache for formations
        self.data_cache.create_cache(
            name='formations',
            cache_type='table',
            size_limit=1000,
            eviction_policy='deny',
            columns=[
                "tbl_key",
                "user_id",
                "name",
                "formation_type",
                "exchange",
                "market",
                "timeframe",
                "lines_json",
                "color",
                "visible",
                "created_at",
                "updated_at"
            ]
        )

        # In-memory cache of Formation objects
        self.formations: Dict[str, Formation] = {}

        # Load existing formations from database
        self._load_formations_from_db()

    def _ensure_table_exists(self) -> None:
        """Create the formations table in the database if it doesn't exist."""
        try:
            if not self.data_cache.db.table_exists(self.TABLE_NAME):
                create_sql = """
                    CREATE TABLE IF NOT EXISTS formations (
                        id INTEGER PRIMARY KEY AUTOINCREMENT,
                        tbl_key TEXT UNIQUE NOT NULL,
                        user_id INTEGER NOT NULL,
                        name TEXT NOT NULL,
                        formation_type TEXT NOT NULL,
                        exchange TEXT NOT NULL,
                        market TEXT NOT NULL,
                        timeframe TEXT NOT NULL,
                        lines_json TEXT NOT NULL,
                        color TEXT DEFAULT '#667eea',
                        visible INTEGER DEFAULT 1,
                        created_at INTEGER NOT NULL,
                        updated_at INTEGER NOT NULL,
                        UNIQUE(user_id, name, exchange, market, timeframe)
                    )
                """
                self.data_cache.db.execute_sql(create_sql, params=[])

                # Create index for scope queries
                index_sql = """
                    CREATE INDEX IF NOT EXISTS idx_formations_scope
                    ON formations(user_id, exchange, market, timeframe)
                """
                self.data_cache.db.execute_sql(index_sql, params=[])

                logger.info("Created formations table in database")
        except Exception as e:
            logger.error(f"Error ensuring formations table exists: {e}", exc_info=True)

    def _load_formations_from_db(self) -> None:
        """Load all formations from database into memory."""
        try:
            formations_df = self.data_cache.get_all_rows_from_datacache(cache_name='formations')
            if formations_df is not None and not formations_df.empty:
                for _, row in formations_df.iterrows():
                    formation = Formation(
                        tbl_key=row.get('tbl_key', ''),
                        user_id=int(row.get('user_id', 0)),
                        name=row.get('name', ''),
                        formation_type=row.get('formation_type', ''),
                        exchange=row.get('exchange', ''),
                        market=row.get('market', ''),
                        timeframe=row.get('timeframe', ''),
                        lines_json=row.get('lines_json', '{}'),
                        color=row.get('color', '#667eea'),
                        visible=bool(row.get('visible', True)),
                        created_at=int(row.get('created_at', 0)),
                        updated_at=int(row.get('updated_at', 0))
                    )
                    self.formations[formation.tbl_key] = formation
                logger.info(f"Loaded {len(self.formations)} formations from database")
        except Exception as e:
            logger.error(f"Error loading formations from database: {e}", exc_info=True)

    def create(self, user_id: int, data: dict) -> dict:
        """
        Create a new formation.

        :param user_id: ID of the user creating the formation
        :param data: Dictionary containing formation data
        :return: Dictionary with success status and formation data or error message
        """
        try:
            # Validate required fields
            required_fields = ['name', 'formation_type', 'exchange', 'market', 'timeframe', 'lines_json']
            for field in required_fields:
                if field not in data or not data[field]:
                    return {"success": False, "message": f"Missing required field: {field}"}

            # Check for duplicate name in same scope
            name = data['name']
            exchange = data['exchange']
            market = data['market']
            timeframe = data['timeframe']

            for formation in self.formations.values():
                if (formation.user_id == user_id and
                    formation.name == name and
                    formation.exchange == exchange and
                    formation.market == market and
                    formation.timeframe == timeframe):
                    return {"success": False, "message": "A formation with this name already exists in this scope"}

            # Generate unique key and timestamps
            tbl_key = str(uuid.uuid4())
            now = int(time.time())

            # Prepare formation data
            columns = (
                "tbl_key", "user_id", "name", "formation_type", "exchange",
                "market", "timeframe", "lines_json", "color", "visible",
                "created_at", "updated_at"
            )

            values = (
                tbl_key,
                user_id,
                name,
                data['formation_type'],
                exchange,
                market,
                timeframe,
                data['lines_json'] if isinstance(data['lines_json'], str) else json.dumps(data['lines_json']),
                data.get('color', '#667eea'),
                1,  # visible
                now,
                now
            )

            # Insert into database/cache via DataCache
            self.data_cache.insert_row_into_datacache(
                cache_name='formations',
                columns=columns,
                values=values
            )

            # Create Formation object and add to memory cache
            formation = Formation(
                tbl_key=tbl_key,
                user_id=user_id,
                name=name,
                formation_type=data['formation_type'],
                exchange=exchange,
                market=market,
                timeframe=timeframe,
                lines_json=data['lines_json'] if isinstance(data['lines_json'], str) else json.dumps(data['lines_json']),
                color=data.get('color', '#667eea'),
                visible=True,
                created_at=now,
                updated_at=now
            )
            self.formations[tbl_key] = formation

            logger.info(f"Created formation '{name}' with tbl_key {tbl_key}")

            return {
                "success": True,
                "message": "Formation created successfully",
                "formation": self._formation_to_dict(formation)
            }

        except Exception as e:
            logger.error(f"Error creating formation: {e}", exc_info=True)
            return {"success": False, "message": f"Error creating formation: {str(e)}"}

    def update(self, user_id: int, data: dict) -> dict:
        """
        Update an existing formation.

        :param user_id: ID of the user updating the formation
        :param data: Dictionary containing formation data with tbl_key
        :return: Dictionary with success status
        """
        try:
            tbl_key = data.get('tbl_key')
            if not tbl_key:
                return {"success": False, "message": "Missing tbl_key"}

            # Find existing formation
            formation = self.formations.get(tbl_key)
            if not formation:
                return {"success": False, "message": "Formation not found"}

            # Verify ownership
            if formation.user_id != user_id:
                return {"success": False, "message": "Not authorized to edit this formation"}

            # Update fields
            now = int(time.time())
            update_data = {
                'updated_at': now
            }

            if 'name' in data:
                formation.name = data['name']
                update_data['name'] = data['name']

            if 'lines_json' in data:
                lines_json = data['lines_json'] if isinstance(data['lines_json'], str) else json.dumps(data['lines_json'])
                formation.lines_json = lines_json
                update_data['lines_json'] = lines_json

            if 'color' in data:
                formation.color = data['color']
                update_data['color'] = data['color']

            if 'visible' in data:
                formation.visible = bool(data['visible'])
                update_data['visible'] = int(data['visible'])

            formation.updated_at = now

            # Update in database/cache
            self.data_cache.modify_datacache_item(
                cache_name='formations',
                filter_vals=[('tbl_key', tbl_key)],
                field_names=tuple(update_data.keys()),
                new_values=tuple(update_data.values()),
                key=tbl_key,
                overwrite='tbl_key'
            )

            logger.info(f"Updated formation '{formation.name}' (tbl_key: {tbl_key})")

            return {
                "success": True,
                "message": "Formation updated successfully",
                "formation": self._formation_to_dict(formation)
            }

        except Exception as e:
            logger.error(f"Error updating formation: {e}", exc_info=True)
            return {"success": False, "message": f"Error updating formation: {str(e)}"}

    def delete(self, user_id: int, tbl_key: str) -> dict:
        """
        Delete a formation.

        :param user_id: ID of the user deleting the formation
        :param tbl_key: Unique key of the formation to delete
        :return: Dictionary with success status
        """
        try:
            # Find existing formation
            formation = self.formations.get(tbl_key)
            if not formation:
                return {"success": False, "message": "Formation not found"}

            # Verify ownership
            if formation.user_id != user_id:
                return {"success": False, "message": "Not authorized to delete this formation"}

            # Remove from database/cache
            self.data_cache.remove_row_from_datacache(
                cache_name='formations',
                filter_vals=[('tbl_key', tbl_key)]
            )

            # Remove from memory cache
            del self.formations[tbl_key]

            logger.info(f"Deleted formation '{formation.name}' (tbl_key: {tbl_key})")

            return {
                "success": True,
                "message": "Formation deleted successfully",
                "tbl_key": tbl_key
            }

        except Exception as e:
            logger.error(f"Error deleting formation: {e}", exc_info=True)
            return {"success": False, "message": f"Error deleting formation: {str(e)}"}

    def get_for_scope(self, user_id: int, exchange: str, market: str, timeframe: str) -> List[dict]:
        """
        Get all formations for a specific scope (exchange/market/timeframe).

        :param user_id: ID of the user
        :param exchange: Exchange name
        :param market: Market/trading pair
        :param timeframe: Timeframe
        :return: List of formation dictionaries
        """
        result = []
        for formation in self.formations.values():
            if (formation.user_id == user_id and
                formation.exchange == exchange and
                formation.market == market and
                formation.timeframe == timeframe):
                result.append(self._formation_to_dict(formation))
        return result

    def get_by_tbl_key(self, user_id: int, tbl_key: str) -> Optional[dict]:
        """
        Get a formation by its tbl_key.

        :param user_id: ID of the user (for ownership verification)
        :param tbl_key: Unique key of the formation
        :return: Formation dictionary or None
        """
        formation = self.formations.get(tbl_key)
        if formation and formation.user_id == user_id:
            return self._formation_to_dict(formation)
        return None

    def get_by_tbl_key_for_strategy(self, tbl_key: str, owner_user_id: int) -> Optional[dict]:
        """
        Get a formation by tbl_key for strategy execution.

        This uses the strategy owner's formations, not the current user's.
        Parallel to indicator_owner_id pattern.

        :param tbl_key: Unique key of the formation
        :param owner_user_id: User ID of the strategy owner
        :return: Formation dictionary or None
        """
        formation = self.formations.get(tbl_key)
        if formation and formation.user_id == owner_user_id:
            return self._formation_to_dict(formation)
        return None

    def calculate_line_value(self, line: dict, timestamp: int) -> float:
        """
        Calculate price at timestamp using linear interpolation/extrapolation.

        This implements infinite line extension - works for any timestamp,
        past or future, by extrapolating the line defined by two points.

        :param line: Dict with point1 and point2, each having 'time' and 'price'
        :param timestamp: Unix timestamp in seconds UTC
        :return: Extrapolated price value
        """
        t1 = line['point1']['time']
        p1 = line['point1']['price']
        t2 = line['point2']['time']
        p2 = line['point2']['price']

        # Handle vertical line (same timestamp)
        if t1 == t2:
            logger.warning(f"Vertical line detected (t1==t2={t1}), returning average price")
            return (p1 + p2) / 2

        # Calculate slope and extrapolate
        slope = (p2 - p1) / (t2 - t1)
        return p1 + slope * (timestamp - t1)

    def get_property_value(self, formation: dict, property_name: str, timestamp: int) -> Optional[float]:
        """
        Get the value of a formation property at a given timestamp.

        :param formation: Formation dictionary
        :param property_name: Name of the property ('line', 'upper', 'lower', 'midline', etc.)
        :param timestamp: Unix timestamp in seconds UTC
        :return: Price value or None
        """
        try:
            lines_data = json.loads(formation.get('lines_json', '{}'))
            lines = lines_data.get('lines', [])

            if not lines:
                return None

            formation_type = formation.get('formation_type', '')

            # Support/Resistance - single line
            if formation_type == 'support_resistance':
                if property_name == 'line' and len(lines) > 0:
                    return self.calculate_line_value(lines[0], timestamp)

            # Channel - two parallel lines
            elif formation_type == 'channel':
                if property_name == 'upper' and len(lines) > 0:
                    return self.calculate_line_value(lines[0], timestamp)
                elif property_name == 'lower' and len(lines) > 1:
                    return self.calculate_line_value(lines[1], timestamp)
                elif property_name == 'midline' and len(lines) >= 2:
                    upper = self.calculate_line_value(lines[0], timestamp)
                    lower = self.calculate_line_value(lines[1], timestamp)
                    return (upper + lower) / 2

            # Default: return first line value
            if len(lines) > 0:
                return self.calculate_line_value(lines[0], timestamp)

            return None

        except Exception as e:
            logger.error(f"Error getting property value: {e}", exc_info=True)
            return None

    def _formation_to_dict(self, formation: Formation) -> dict:
        """Convert Formation object to dictionary."""
        return {
            'tbl_key': formation.tbl_key,
            'user_id': formation.user_id,
            'name': formation.name,
            'formation_type': formation.formation_type,
            'exchange': formation.exchange,
            'market': formation.market,
            'timeframe': formation.timeframe,
            'lines_json': formation.lines_json,
            'color': formation.color,
            'visible': formation.visible,
            'created_at': formation.created_at,
            'updated_at': formation.updated_at
        }
