# models.py
#
# Copyright 2025 Pavel Baksy
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# 
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# SPDX-License-Identifier: GPL-3.0-or-later

from dataclasses import dataclass, asdict
from typing import Dict, Optional, List
from . import constants


@dataclass
class HttpRequest:
    """Represents an HTTP request."""
    method: str  # "GET", "POST", "PUT", "DELETE"
    url: str
    headers: Dict[str, str]  # Key-value pairs
    body: str  # Raw text body
    syntax: str = "RAW"  # Syntax highlighting: "RAW", "JSON", or "XML"

    @classmethod
    def default_headers(cls) -> Dict[str, str]:
        """Return default headers for new requests."""
        # Use only major.minor version (without patch number)
        version_parts = constants.VERSION.split(".")
        short_version = ".".join(version_parts[:2])
        return {
            "User-Agent": f"Roster/{short_version}"
        }

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        # Provide default for syntax field for backward compatibility
        if 'syntax' not in data:
            data = {**data, 'syntax': 'RAW'}
        return cls(**data)


@dataclass
class HttpResponse:
    """Represents an HTTP response."""
    status_code: int
    status_text: str  # e.g., "OK"
    headers: str  # Raw header text from libsoup3
    body: str  # Raw body text
    response_time_ms: float
    response_size_bytes: int = 0  # Total response size in bytes

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return asdict(self)

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(**data)


@dataclass
class HistoryEntry:
    """Represents a request/response pair in history."""
    timestamp: str  # ISO format datetime
    request: HttpRequest
    response: Optional[HttpResponse]
    error: Optional[str]  # Error message if request failed
    id: str = None  # Unique identifier for the entry
    has_redacted_variables: bool = False  # True if sensitive variables were redacted

    def __post_init__(self):
        """Generate UUID if id not provided."""
        if self.id is None:
            import uuid
            self.id = str(uuid.uuid4())

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            'id': self.id,
            'timestamp': self.timestamp,
            'request': self.request.to_dict(),
            'response': self.response.to_dict() if self.response else None,
            'error': self.error,
            'has_redacted_variables': self.has_redacted_variables
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(
            id=data.get('id'),  # Backwards compatible - will generate if missing
            timestamp=data['timestamp'],
            request=HttpRequest.from_dict(data['request']),
            response=HttpResponse.from_dict(data['response']) if data.get('response') else None,
            error=data.get('error'),
            has_redacted_variables=data.get('has_redacted_variables', False)
        )


@dataclass
class SavedRequest:
    """A saved HTTP request with metadata."""
    id: str  # UUID
    name: str  # User-provided name
    request: HttpRequest
    created_at: str  # ISO format
    modified_at: str  # ISO format
    scripts: Optional['Scripts'] = None  # Scripts for preprocessing and postprocessing

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            'id': self.id,
            'name': self.name,
            'request': self.request.to_dict(),
            'created_at': self.created_at,
            'modified_at': self.modified_at,
            'scripts': self.scripts.to_dict() if self.scripts else None
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(
            id=data['id'],
            name=data['name'],
            request=HttpRequest.from_dict(data['request']),
            created_at=data['created_at'],
            modified_at=data['modified_at'],
            scripts=Scripts.from_dict(data['scripts']) if data.get('scripts') else None
        )


@dataclass
class Environment:
    """An environment with variable values."""
    id: str  # UUID
    name: str  # e.g., "production", "integration", "stage"
    variables: Dict[str, str]  # variable_name -> value
    created_at: str  # ISO format

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            'id': self.id,
            'name': self.name,
            'variables': self.variables,
            'created_at': self.created_at
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(
            id=data['id'],
            name=data['name'],
            variables=data.get('variables', {}),
            created_at=data['created_at']
        )


@dataclass
class Project:
    """A project containing saved requests and environments."""
    id: str  # UUID
    name: str
    requests: List[SavedRequest]
    created_at: str  # ISO format
    icon: str = "folder-symbolic"  # Icon name
    variable_names: List[str] = None  # List of variable names defined for this project
    environments: List[Environment] = None  # List of environments
    sensitive_variables: List[str] = None  # List of variable names marked as sensitive (stored in keyring)

    def __post_init__(self):
        """Initialize optional fields."""
        if self.variable_names is None:
            self.variable_names = []
        if self.environments is None:
            self.environments = []
        if self.sensitive_variables is None:
            self.sensitive_variables = []

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            'id': self.id,
            'name': self.name,
            'requests': [req.to_dict() for req in self.requests],
            'created_at': self.created_at,
            'icon': self.icon,
            'variable_names': self.variable_names,
            'environments': [env.to_dict() for env in self.environments],
            'sensitive_variables': self.sensitive_variables
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(
            id=data['id'],
            name=data['name'],
            requests=[SavedRequest.from_dict(r) for r in data.get('requests', [])],
            created_at=data['created_at'],
            icon=data.get('icon', 'folder-symbolic'),  # Default for old data
            variable_names=data.get('variable_names', []),
            environments=[Environment.from_dict(e) for e in data.get('environments', [])],
            sensitive_variables=data.get('sensitive_variables', [])  # Default for old data
        )


@dataclass
class RequestTab:
    """Represents an open request tab in the UI."""
    id: str  # UUID for tab identification
    name: str  # Display name (from SavedRequest or URL)
    request: HttpRequest  # Current request state
    response: Optional[HttpResponse] = None  # Last response (if sent)
    saved_request_id: Optional[str] = None  # ID if from SavedRequest
    modified: bool = False  # True if has unsaved changes
    original_request: Optional[HttpRequest] = None  # For change detection
    project_id: Optional[str] = None  # Project association for environment variables
    selected_environment_id: Optional[str] = None  # Selected environment for variable substitution
    scripts: Optional['Scripts'] = None  # Scripts for preprocessing and postprocessing

    def is_modified(self) -> bool:
        """Check if current request differs from original."""
        if not self.original_request:
            # New unsaved request - consider modified if has content
            return bool(self.request.url or self.request.body or self.request.headers)

        return (
            self.request.method != self.original_request.method or
            self.request.url != self.original_request.url or
            self.request.headers != self.original_request.headers or
            self.request.body != self.original_request.body or
            self.request.syntax != self.original_request.syntax
        )

    def to_dict(self):
        """Convert to dictionary for JSON serialization (for session persistence)."""
        return {
            'id': self.id,
            'name': self.name,
            'request': self.request.to_dict(),
            'response': self.response.to_dict() if self.response else None,
            'saved_request_id': self.saved_request_id,
            'modified': self.modified,
            'original_request': self.original_request.to_dict() if self.original_request else None,
            'project_id': self.project_id,
            'selected_environment_id': self.selected_environment_id,
            'scripts': self.scripts.to_dict() if self.scripts else None
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary (for session restoration)."""
        return cls(
            id=data['id'],
            name=data['name'],
            request=HttpRequest.from_dict(data['request']),
            response=HttpResponse.from_dict(data['response']) if data.get('response') else None,
            saved_request_id=data.get('saved_request_id'),
            modified=data.get('modified', False),
            original_request=HttpRequest.from_dict(data['original_request']) if data.get('original_request') else None,
            project_id=data.get('project_id'),
            selected_environment_id=data.get('selected_environment_id'),
            scripts=Scripts.from_dict(data['scripts']) if data.get('scripts') else None
        )


@dataclass
class Scripts:
    """Scripts for request preprocessing and postprocessing."""
    preprocessing: str = ""
    postprocessing: str = ""

    def to_dict(self):
        """Convert to dictionary for JSON serialization."""
        return {
            'preprocessing': self.preprocessing,
            'postprocessing': self.postprocessing
        }

    @classmethod
    def from_dict(cls, data):
        """Create instance from dictionary."""
        return cls(
            preprocessing=data.get('preprocessing', ''),
            postprocessing=data.get('postprocessing', '')
        )
