"""
GraphQL resolvers for MarkiTect data.

Implements the resolver functions that fetch data from MarkiTect's
database and services to fulfill GraphQL queries.
"""

import json
import sqlite3
import os
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Dict, Any, Union

from jsonpath_ng import parse as jsonpath_parse

from ..database import DatabaseManager
from ..ast_service import ASTService
from .schema import (
    MarkdownFile, Schema, AST, ASTNode, DatabaseStats,
    SearchResult, Query as QueryType
)


class MarkiTectResolver:
    """Base resolver class with common database operations."""

    def __init__(self, db_path: str):
        """Initialize resolver with database path."""
        self.db_path = db_path
        self.db_manager = DatabaseManager(db_path)
        self.ast_service = ASTService()

    def get_connection(self):
        """Get database connection."""
        return sqlite3.connect(self.db_path)

    def row_to_dict(self, cursor, row):
        """Convert database row to dictionary."""
        return dict(zip([col[0] for col in cursor.description], row))


class Query(QueryType):
    """GraphQL query resolver implementation."""

    def __init__(self):
        """Initialize query resolver."""
        # Default database path - could be made configurable
        self.resolver = MarkiTectResolver(get_default_database_path())

    def resolve_markdown_file(self, info, id=None, filename=None):
        """Resolve single markdown file query."""
        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        if id:
            cursor.execute(
                "SELECT * FROM markdown_files WHERE id = ?",
                (id,)
            )
        elif filename:
            cursor.execute(
                "SELECT * FROM markdown_files WHERE filename = ?",
                (filename,)
            )
        else:
            return None

        row = cursor.fetchone()
        conn.close()

        if row:
            data = self.resolver.row_to_dict(cursor, row)
            # Parse front matter JSON
            if data['front_matter']:
                try:
                    data['front_matter_raw'] = json.loads(data['front_matter'])
                except json.JSONDecodeError:
                    data['front_matter_raw'] = {}
            else:
                data['front_matter_raw'] = {}

            return MarkdownFile(**data)
        return None

    def resolve_schema(self, info, id=None, filename=None):
        """Resolve single schema query."""
        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        if id:
            cursor.execute(
                "SELECT * FROM schemas WHERE id = ?",
                (id,)
            )
        elif filename:
            cursor.execute(
                "SELECT * FROM schemas WHERE filename = ?",
                (filename,)
            )
        else:
            return None

        row = cursor.fetchone()
        conn.close()

        if row:
            data = self.resolver.row_to_dict(cursor, row)
            # Parse schema content JSON
            if data['schema_content']:
                try:
                    data['schema_content'] = json.loads(data['schema_content'])
                except json.JSONDecodeError:
                    data['schema_content'] = {}

            return Schema(**data)
        return None

    def resolve_ast(self, info, file_id=None, filename=None):
        """Resolve AST query."""
        if not file_id and not filename:
            return None

        # Get file path
        if file_id:
            conn = self.resolver.get_connection()
            cursor = conn.cursor()
            cursor.execute(
                "SELECT filename FROM markdown_files WHERE id = ?",
                (file_id,)
            )
            row = cursor.fetchone()
            conn.close()
            if not row:
                return None
            filename = row[0]

        if not filename:
            return None

        file_path = Path(filename)
        try:
            # Use AST service to get parsed AST
            ast_result = self.resolver.ast_service.display_ast(file_path, "json")
            if ast_result.get('success'):
                ast_data = ast_result.get('ast', {})

                # Convert to our GraphQL AST format
                return AST(
                    file_id=file_id,
                    filename=filename,
                    tree=self._convert_ast_nodes(ast_data),
                    metadata=ast_result.get('metadata', {}),
                    heading_count=self._count_nodes_by_type(ast_data, 'heading'),
                    link_count=self._count_nodes_by_type(ast_data, 'link'),
                    image_count=self._count_nodes_by_type(ast_data, 'image'),
                    code_block_count=self._count_nodes_by_type(ast_data, 'code')
                )
        except Exception:
            pass

        return None

    def resolve_markdown_files(self, info, limit=50, offset=0, has_front_matter=None, created_after=None):
        """Resolve markdown files list query."""
        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        # Build query with filters
        query = "SELECT * FROM markdown_files WHERE 1=1"
        params = []

        if has_front_matter is not None:
            if has_front_matter:
                query += " AND front_matter IS NOT NULL AND front_matter != ''"
            else:
                query += " AND (front_matter IS NULL OR front_matter = '')"

        if created_after:
            query += " AND created_at > ?"
            params.append(created_after.isoformat())

        query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
        params.extend([limit, offset])

        cursor.execute(query, params)
        rows = cursor.fetchall()
        conn.close()

        files = []
        for row in rows:
            data = self.resolver.row_to_dict(cursor, row)
            # Parse front matter JSON
            if data['front_matter']:
                try:
                    data['front_matter_raw'] = json.loads(data['front_matter'])
                except json.JSONDecodeError:
                    data['front_matter_raw'] = {}
            else:
                data['front_matter_raw'] = {}

            files.append(MarkdownFile(**data))

        return files

    def resolve_schemas(self, info, limit=50, offset=0):
        """Resolve schemas list query."""
        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        cursor.execute(
            "SELECT * FROM schemas ORDER BY created_at DESC LIMIT ? OFFSET ?",
            (limit, offset)
        )
        rows = cursor.fetchall()
        conn.close()

        schemas = []
        for row in rows:
            data = self.resolver.row_to_dict(cursor, row)
            # Parse schema content JSON
            if data['schema_content']:
                try:
                    data['schema_content'] = json.loads(data['schema_content'])
                except json.JSONDecodeError:
                    data['schema_content'] = {}

            schemas.append(Schema(**data))

        return schemas

    def resolve_search(self, info, query, type="all", limit=20):
        """Resolve search query."""
        results = []

        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        # Search in markdown files
        if type in ["all", "file"]:
            cursor.execute("""
                SELECT *, 'file' as result_type FROM markdown_files
                WHERE filename LIKE ? OR content LIKE ?
                ORDER BY
                    CASE WHEN filename LIKE ? THEN 1 ELSE 2 END,
                    created_at DESC
                LIMIT ?
            """, (f"%{query}%", f"%{query}%", f"%{query}%", limit))

            for row in cursor.fetchall():
                data = self.resolver.row_to_dict(cursor, row)
                if data['front_matter']:
                    try:
                        data['front_matter_raw'] = json.loads(data['front_matter'])
                    except json.JSONDecodeError:
                        data['front_matter_raw'] = {}
                else:
                    data['front_matter_raw'] = {}

                # Remove extra fields that don't belong to MarkdownFile
                file_data = {k: v for k, v in data.items() if k != 'result_type'}

                # Calculate basic relevance score
                score = 1.0
                if query.lower() in data['filename'].lower():
                    score += 0.5
                if data['content'] and query.lower() in data['content'].lower():
                    score += 0.3

                results.append(SearchResult(
                    type="file",
                    score=score,
                    file=MarkdownFile(**file_data),
                    highlight=self._extract_highlight(data.get('content', ''), query)
                ))

        # Search in schemas
        if type in ["all", "schema"]:
            cursor.execute("""
                SELECT *, 'schema' as result_type FROM schemas
                WHERE filename LIKE ? OR title LIKE ? OR description LIKE ?
                ORDER BY created_at DESC
                LIMIT ?
            """, (f"%{query}%", f"%{query}%", f"%{query}%", limit))

            for row in cursor.fetchall():
                data = self.resolver.row_to_dict(cursor, row)
                if data['schema_content']:
                    try:
                        data['schema_content'] = json.loads(data['schema_content'])
                    except json.JSONDecodeError:
                        data['schema_content'] = {}

                # Remove extra fields that don't belong to Schema
                schema_data = {k: v for k, v in data.items() if k != 'result_type'}

                # Calculate basic relevance score
                score = 1.0
                if query.lower() in data.get('title', '').lower():
                    score += 0.5

                results.append(SearchResult(
                    type="schema",
                    score=score,
                    schema=Schema(**schema_data),
                    highlight=data.get('title', '') or data.get('filename', '')
                ))

        conn.close()

        # Sort by score and limit
        results.sort(key=lambda x: x.score, reverse=True)
        return results[:limit]

    def resolve_database_stats(self, info):
        """Resolve database statistics."""
        conn = self.resolver.get_connection()
        cursor = conn.cursor()

        # Count files
        cursor.execute("SELECT COUNT(*) FROM markdown_files")
        total_files = cursor.fetchone()[0]

        # Count schemas
        cursor.execute("SELECT COUNT(*) FROM schemas")
        total_schemas = cursor.fetchone()[0]

        # Get database size
        db_size = 0
        if os.path.exists(self.resolver.db_path):
            db_size = os.path.getsize(self.resolver.db_path)

        # Get last update time
        cursor.execute("""
            SELECT MAX(created_at) FROM (
                SELECT created_at FROM markdown_files
                UNION ALL
                SELECT created_at FROM schemas
            )
        """)
        last_updated_str = cursor.fetchone()[0]
        last_updated = None
        if last_updated_str:
            try:
                last_updated = datetime.fromisoformat(last_updated_str)
            except ValueError:
                pass

        conn.close()

        return DatabaseStats(
            total_files=total_files,
            total_schemas=total_schemas,
            total_size_bytes=db_size,
            last_updated=last_updated
        )

    def resolve_ast_query(self, info, jsonpath, file_id=None, filename=None):
        """Resolve JSONPath query on AST."""
        if not file_id and not filename:
            return []

        # Get AST data
        ast = self.resolve_ast(info, file_id=file_id, filename=filename)
        if not ast or not ast.metadata:
            return []

        try:
            # Parse JSONPath expression
            jsonpath_expr = jsonpath_parse(jsonpath)

            # Apply to AST metadata (contains the raw AST)
            matches = jsonpath_expr.find(ast.metadata)

            # Return the matched values
            return [match.value for match in matches]
        except Exception:
            return []

    def _convert_ast_nodes(self, ast_data):
        """Convert AST data to GraphQL ASTNode format."""
        if not ast_data or not isinstance(ast_data, dict):
            return []

        nodes = []
        if 'children' in ast_data:
            for child in ast_data['children']:
                node = ASTNode(
                    type=child.get('type', 'unknown'),
                    value=child.get('value'),
                    level=child.get('depth'),
                    attrs=child,
                    children=self._convert_ast_nodes(child) if 'children' in child else []
                )
                nodes.append(node)

        return nodes

    def _count_nodes_by_type(self, ast_data, node_type):
        """Count nodes of specific type in AST."""
        if not ast_data or not isinstance(ast_data, dict):
            return 0

        count = 0
        if ast_data.get('type') == node_type:
            count += 1

        if 'children' in ast_data:
            for child in ast_data['children']:
                count += self._count_nodes_by_type(child, node_type)

        return count

    def _extract_highlight(self, content, query, context_length=100):
        """Extract highlighted snippet from content."""
        if not content or not query:
            return ""

        query_lower = query.lower()
        content_lower = content.lower()

        index = content_lower.find(query_lower)
        if index == -1:
            return content[:context_length] + "..." if len(content) > context_length else content

        start = max(0, index - context_length // 2)
        end = min(len(content), index + len(query) + context_length // 2)

        snippet = content[start:end]
        if start > 0:
            snippet = "..." + snippet
        if end < len(content):
            snippet = snippet + "..."

        return snippet


class Mutation:
    """GraphQL mutation resolver implementation."""

    def __init__(self):
        """Initialize mutation resolver."""
        self.resolver = MarkiTectResolver(get_default_database_path())

    def resolve_add_markdown_file(self, info, filename, content):
        """Add a new markdown file to the database."""
        try:
            # Store the file using the database manager
            file_id = self.resolver.db_manager.store_markdown_file(filename, content)

            if file_id:
                # Retrieve the created file
                conn = self.resolver.get_connection()
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM markdown_files WHERE id = ?", (file_id,))
                row = cursor.fetchone()
                conn.close()

                if row:
                    data = self.resolver.row_to_dict(cursor, row)
                    # Parse front matter JSON
                    if data['front_matter']:
                        try:
                            data['front_matter_raw'] = json.loads(data['front_matter'])
                        except json.JSONDecodeError:
                            data['front_matter_raw'] = {}
                    else:
                        data['front_matter_raw'] = {}

                    from .schema import AddMarkdownFilePayload, MarkdownFile
                    return AddMarkdownFilePayload(
                        markdown_file=MarkdownFile(**data),
                        success=True,
                        errors=[]
                    )

            from .schema import AddMarkdownFilePayload
            return AddMarkdownFilePayload(
                markdown_file=None,
                success=False,
                errors=["Failed to store markdown file"]
            )

        except Exception as e:
            from .schema import AddMarkdownFilePayload
            return AddMarkdownFilePayload(
                markdown_file=None,
                success=False,
                errors=[str(e)]
            )

    def resolve_update_markdown_file(self, info, id, content=None):
        """Update an existing markdown file."""
        try:
            if not content:
                from .schema import UpdateMarkdownFilePayload
                return UpdateMarkdownFilePayload(
                    markdown_file=None,
                    success=False,
                    errors=["Content is required for update"]
                )

            conn = self.resolver.get_connection()
            cursor = conn.cursor()

            # Check if file exists
            cursor.execute("SELECT filename FROM markdown_files WHERE id = ?", (id,))
            row = cursor.fetchone()
            if not row:
                conn.close()
                from .schema import UpdateMarkdownFilePayload
                return UpdateMarkdownFilePayload(
                    markdown_file=None,
                    success=False,
                    errors=[f"Markdown file with ID {id} not found"]
                )

            filename = row[0]
            conn.close()

            # Update using store_markdown_file (it handles front matter parsing)
            file_id = self.resolver.db_manager.store_markdown_file(filename, content)

            if file_id:
                # Retrieve the updated file
                conn = self.resolver.get_connection()
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM markdown_files WHERE filename = ?", (filename,))
                row = cursor.fetchone()
                conn.close()

                if row:
                    data = self.resolver.row_to_dict(cursor, row)
                    # Parse front matter JSON
                    if data['front_matter']:
                        try:
                            data['front_matter_raw'] = json.loads(data['front_matter'])
                        except json.JSONDecodeError:
                            data['front_matter_raw'] = {}
                    else:
                        data['front_matter_raw'] = {}

                    from .schema import UpdateMarkdownFilePayload, MarkdownFile
                    return UpdateMarkdownFilePayload(
                        markdown_file=MarkdownFile(**data),
                        success=True,
                        errors=[]
                    )

            from .schema import UpdateMarkdownFilePayload
            return UpdateMarkdownFilePayload(
                markdown_file=None,
                success=False,
                errors=["Failed to update markdown file"]
            )

        except Exception as e:
            from .schema import UpdateMarkdownFilePayload
            return UpdateMarkdownFilePayload(
                markdown_file=None,
                success=False,
                errors=[str(e)]
            )

    def resolve_add_schema(self, info, filename, schema_content):
        """Add a new JSON schema to the database."""
        try:
            # Store the schema using the database manager
            schema_id = self.resolver.db_manager.store_schema_file(filename, schema_content)

            if schema_id:
                # Retrieve the created schema
                conn = self.resolver.get_connection()
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM schemas WHERE id = ?", (schema_id,))
                row = cursor.fetchone()
                conn.close()

                if row:
                    data = self.resolver.row_to_dict(cursor, row)
                    # Parse schema content JSON
                    if data['schema_content']:
                        try:
                            data['schema_content'] = json.loads(data['schema_content'])
                        except json.JSONDecodeError:
                            data['schema_content'] = {}

                    from .schema import AddSchemaPayload, Schema
                    return AddSchemaPayload(
                        schema=Schema(**data),
                        success=True,
                        errors=[]
                    )

            from .schema import AddSchemaPayload
            return AddSchemaPayload(
                schema=None,
                success=False,
                errors=["Failed to store schema"]
            )

        except Exception as e:
            from .schema import AddSchemaPayload
            return AddSchemaPayload(
                schema=None,
                success=False,
                errors=[str(e)]
            )

    def resolve_update_schema(self, info, id, schema_content=None):
        """Update an existing JSON schema."""
        try:
            if not schema_content:
                from .schema import UpdateSchemaPayload
                return UpdateSchemaPayload(
                    schema=None,
                    success=False,
                    errors=["Schema content is required for update"]
                )

            conn = self.resolver.get_connection()
            cursor = conn.cursor()

            # Check if schema exists
            cursor.execute("SELECT filename FROM schemas WHERE id = ?", (id,))
            row = cursor.fetchone()
            if not row:
                conn.close()
                from .schema import UpdateSchemaPayload
                return UpdateSchemaPayload(
                    schema=None,
                    success=False,
                    errors=[f"Schema with ID {id} not found"]
                )

            filename = row[0]
            conn.close()

            # Update using store_schema_file
            schema_id = self.resolver.db_manager.store_schema_file(filename, schema_content)

            if schema_id:
                # Retrieve the updated schema
                conn = self.resolver.get_connection()
                cursor = conn.cursor()
                cursor.execute("SELECT * FROM schemas WHERE filename = ?", (filename,))
                row = cursor.fetchone()
                conn.close()

                if row:
                    data = self.resolver.row_to_dict(cursor, row)
                    # Parse schema content JSON
                    if data['schema_content']:
                        try:
                            data['schema_content'] = json.loads(data['schema_content'])
                        except json.JSONDecodeError:
                            data['schema_content'] = {}

                    from .schema import UpdateSchemaPayload, Schema
                    return UpdateSchemaPayload(
                        schema=Schema(**data),
                        success=True,
                        errors=[]
                    )

            from .schema import UpdateSchemaPayload
            return UpdateSchemaPayload(
                schema=None,
                success=False,
                errors=["Failed to update schema"]
            )

        except Exception as e:
            from .schema import UpdateSchemaPayload
            return UpdateSchemaPayload(
                schema=None,
                success=False,
                errors=[str(e)]
            )

    def resolve_delete_schema(self, info, filename):
        """Delete a JSON schema from the database."""
        try:
            # Delete using the database manager
            success = self.resolver.db_manager.delete_schema_file(filename)

            from .schema import DeleteSchemaPayload
            if success:
                return DeleteSchemaPayload(
                    success=True,
                    deleted_filename=filename,
                    errors=[]
                )
            else:
                return DeleteSchemaPayload(
                    success=False,
                    deleted_filename=None,
                    errors=[f"Failed to delete schema: {filename}"]
                )

        except Exception as e:
            from .schema import DeleteSchemaPayload
            return DeleteSchemaPayload(
                success=False,
                deleted_filename=None,
                errors=[str(e)]
            )


def get_default_database_path():
    """Get default database path for GraphQL resolvers."""
    import os
    from pathlib import Path

    # Use the same logic as CLI
    if 'MARKITECT_DB' in os.environ:
        return os.environ['MARKITECT_DB']

    config_dir = Path.home() / '.markitect'
    config_dir.mkdir(exist_ok=True)
    return str(config_dir / 'markitect.db')