"""
UCXL Standard Response and Error Codes Library

This module provides standardized UCXL error and response codes for consistent
cross-service communication and client handling.

Based on UCXL-ERROR-CODES.md and UCXL-RESPONSE-CODES.md v1.0
"""

import json
import uuid
from datetime import datetime, timezone
from enum import Enum
from typing import Any, Dict, Optional
from dataclasses import dataclass, field, asdict


class UCXLErrorCode(Enum):
    """Standard UCXL Error Codes following format: UCXL-<HTTP-class>-<SHORT_NAME>"""
    
    # 400 - Client Errors
    INVALID_ADDRESS = "UCXL-400-INVALID_ADDRESS"
    MISSING_FIELD = "UCXL-400-MISSING_FIELD"
    INVALID_FORMAT = "UCXL-400-INVALID_FORMAT"
    
    # 401 - Authentication
    UNAUTHORIZED = "UCXL-401-UNAUTHORIZED"
    
    # 403 - Authorization
    FORBIDDEN = "UCXL-403-FORBIDDEN"
    
    # 404 - Not Found
    NOT_FOUND = "UCXL-404-NOT_FOUND"
    
    # 409 - Conflict
    CONFLICT = "UCXL-409-CONFLICT"
    
    # 422 - Unprocessable Entity
    UNPROCESSABLE_ENTITY = "UCXL-422-UNPROCESSABLE_ENTITY"
    
    # 429 - Rate Limiting
    RATE_LIMIT = "UCXL-429-RATE_LIMIT"
    
    # 500 - Server Errors
    INTERNAL_ERROR = "UCXL-500-INTERNAL_ERROR"
    
    # 503 - Service Unavailable
    SERVICE_UNAVAILABLE = "UCXL-503-SERVICE_UNAVAILABLE"
    
    # 504 - Gateway Timeout
    GATEWAY_TIMEOUT = "UCXL-504-GATEWAY_TIMEOUT"

    def http_status(self) -> int:
        """Get the HTTP status code for this error code"""
        mapping = {
            self.INVALID_ADDRESS: 400,
            self.MISSING_FIELD: 400,
            self.INVALID_FORMAT: 400,
            self.UNAUTHORIZED: 401,
            self.FORBIDDEN: 403,
            self.NOT_FOUND: 404,
            self.CONFLICT: 409,
            self.UNPROCESSABLE_ENTITY: 422,
            self.RATE_LIMIT: 429,
            self.INTERNAL_ERROR: 500,
            self.SERVICE_UNAVAILABLE: 503,
            self.GATEWAY_TIMEOUT: 504,
        }
        return mapping.get(self, 500)

    def default_message(self) -> str:
        """Get the default error message for this code"""
        messages = {
            self.INVALID_ADDRESS: "Invalid UCXL address format",
            self.MISSING_FIELD: "Required field is missing",
            self.INVALID_FORMAT: "Input does not match the expected format",
            self.UNAUTHORIZED: "Authentication credentials missing or invalid",
            self.FORBIDDEN: "Insufficient permissions for the action",
            self.NOT_FOUND: "Requested resource not found",
            self.CONFLICT: "Conflict with current state",
            self.UNPROCESSABLE_ENTITY: "Semantic validation failed",
            self.RATE_LIMIT: "Too many requests; rate limiting in effect",
            self.INTERNAL_ERROR: "Internal server error",
            self.SERVICE_UNAVAILABLE: "Service is currently unavailable",
            self.GATEWAY_TIMEOUT: "Downstream gateway timed out",
        }
        return messages.get(self, "Unknown error")

    def is_client_error(self) -> bool:
        """Check if this is a client error (4xx)"""
        return 400 <= self.http_status() < 500

    def is_server_error(self) -> bool:
        """Check if this is a server error (5xx)"""
        return 500 <= self.http_status() < 600

    def should_retry(self) -> bool:
        """Check if this error should trigger a retry"""
        return self in {
            self.RATE_LIMIT,
            self.INTERNAL_ERROR,
            self.SERVICE_UNAVAILABLE,
            self.GATEWAY_TIMEOUT,
        }


class UCXLResponseCode(Enum):
    """Standard UCXL Response Codes following format: UCXL-<HTTP-class>-<SHORT_NAME>"""
    
    # 200 - Success
    OK = "UCXL-200-OK"
    
    # 201 - Created
    CREATED = "UCXL-201-CREATED"
    
    # 202 - Accepted (Async)
    ACCEPTED = "UCXL-202-ACCEPTED"
    
    # 204 - No Content
    NO_CONTENT = "UCXL-204-NO_CONTENT"
    
    # 206 - Partial Content
    PARTIAL_CONTENT = "UCXL-206-PARTIAL_CONTENT"
    
    # 304 - Not Modified (Caching)
    NOT_MODIFIED = "UCXL-304-NOT_MODIFIED"

    def http_status(self) -> int:
        """Get the HTTP status code for this response code"""
        mapping = {
            self.OK: 200,
            self.CREATED: 201,
            self.ACCEPTED: 202,
            self.NO_CONTENT: 204,
            self.PARTIAL_CONTENT: 206,
            self.NOT_MODIFIED: 304,
        }
        return mapping.get(self, 200)

    def default_message(self) -> str:
        """Get the default success message for this code"""
        messages = {
            self.OK: "Request completed successfully",
            self.CREATED: "Resource created successfully",
            self.ACCEPTED: "Request accepted for processing",
            self.NO_CONTENT: "Request completed with no content to return",
            self.PARTIAL_CONTENT: "Partial results returned",
            self.NOT_MODIFIED: "Resource not modified since last fetch",
        }
        return messages.get(self, "Success")

    def is_async(self) -> bool:
        """Check if this indicates an asynchronous operation"""
        return self == self.ACCEPTED

    def is_partial(self) -> bool:
        """Check if this indicates partial/incomplete results"""
        return self in {self.PARTIAL_CONTENT, self.ACCEPTED}


@dataclass
class UCXLError:
    """UCXL Error Details"""
    code: UCXLErrorCode
    message: str
    source: str
    path: str
    request_id: str
    timestamp: datetime
    details: Optional[Dict[str, Any]] = None
    cause: Optional[str] = None

    def __post_init__(self):
        if isinstance(self.code, str):
            self.code = UCXLErrorCode(self.code)


@dataclass
class UCXLErrorResponse:
    """UCXL Standard Error Response Payload"""
    error: UCXLError

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        result = asdict(self)
        result['error']['code'] = self.error.code.value
        result['error']['timestamp'] = self.error.timestamp.isoformat()
        return result

    def to_json(self) -> str:
        """Convert to JSON string"""
        return json.dumps(self.to_dict())

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'UCXLErrorResponse':
        """Create from dictionary"""
        error_data = data['error']
        error_data['timestamp'] = datetime.fromisoformat(error_data['timestamp'].replace('Z', '+00:00'))
        return cls(error=UCXLError(**error_data))

    @classmethod
    def from_json(cls, json_str: str) -> 'UCXLErrorResponse':
        """Create from JSON string"""
        return cls.from_dict(json.loads(json_str))


@dataclass
class UCXLResponse:
    """UCXL Response Details"""
    code: UCXLResponseCode
    message: str
    request_id: str
    timestamp: datetime
    data: Optional[Any] = None
    details: Optional[Dict[str, Any]] = None

    def __post_init__(self):
        if isinstance(self.code, str):
            self.code = UCXLResponseCode(self.code)


@dataclass
class UCXLSuccessResponse:
    """UCXL Standard Success Response Payload"""
    response: UCXLResponse

    def to_dict(self) -> Dict[str, Any]:
        """Convert to dictionary"""
        result = asdict(self)
        result['response']['code'] = self.response.code.value
        result['response']['timestamp'] = self.response.timestamp.isoformat()
        return result

    def to_json(self) -> str:
        """Convert to JSON string"""
        return json.dumps(self.to_dict())

    @classmethod
    def from_dict(cls, data: Dict[str, Any]) -> 'UCXLSuccessResponse':
        """Create from dictionary"""
        response_data = data['response']
        response_data['timestamp'] = datetime.fromisoformat(response_data['timestamp'].replace('Z', '+00:00'))
        return cls(response=UCXLResponse(**response_data))

    @classmethod
    def from_json(cls, json_str: str) -> 'UCXLSuccessResponse':
        """Create from JSON string"""
        return cls.from_dict(json.loads(json_str))


class UCXLErrorBuilder:
    """Builder for creating UCXL Error responses"""

    def __init__(self, code: UCXLErrorCode):
        self.code = code
        self.message = None
        self.details = {}
        self.source = "ucxl-api/v1"
        self.path = "/"
        self.request_id = f"req-{str(uuid.uuid4())[:8]}"
        self.cause = None

    def message(self, message: str) -> 'UCXLErrorBuilder':
        """Set a custom error message"""
        self.message = message
        return self

    def field(self, field: str, provided: Any) -> 'UCXLErrorBuilder':
        """Add details about the field that failed"""
        self.details["field"] = field
        self.details["provided"] = provided
        return self

    def expected_format(self, format_str: str) -> 'UCXLErrorBuilder':
        """Add expected format information"""
        self.details["expected_format"] = format_str
        return self

    def detail(self, key: str, value: Any) -> 'UCXLErrorBuilder':
        """Add a detail field"""
        self.details[key] = value
        return self

    def source(self, source: str) -> 'UCXLErrorBuilder':
        """Set the source service"""
        self.source = source
        return self

    def path(self, path: str) -> 'UCXLErrorBuilder':
        """Set the request path"""
        self.path = path
        return self

    def request_id(self, request_id: str) -> 'UCXLErrorBuilder':
        """Set the request ID"""
        self.request_id = request_id
        return self

    def cause(self, cause: str) -> 'UCXLErrorBuilder':
        """Set the error cause"""
        self.cause = cause
        return self

    def build(self) -> UCXLErrorResponse:
        """Build the error response"""
        message = self.message or self.code.default_message()
        details = self.details if self.details else None

        error = UCXLError(
            code=self.code,
            message=message,
            details=details,
            source=self.source,
            path=self.path,
            request_id=self.request_id,
            timestamp=datetime.now(timezone.utc),
            cause=self.cause,
        )
        return UCXLErrorResponse(error=error)


class UCXLResponseBuilder:
    """Builder for creating UCXL Success responses"""

    def __init__(self, code: UCXLResponseCode):
        self.code = code
        self.message = None
        self.data = None
        self.details = {}
        self.request_id = f"req-{str(uuid.uuid4())[:8]}"

    def message(self, message: str) -> 'UCXLResponseBuilder':
        """Set a custom success message"""
        self.message = message
        return self

    def data(self, data: Any) -> 'UCXLResponseBuilder':
        """Set the response data"""
        self.data = data
        return self

    def detail(self, key: str, value: Any) -> 'UCXLResponseBuilder':
        """Add a detail field"""
        self.details[key] = value
        return self

    def request_id(self, request_id: str) -> 'UCXLResponseBuilder':
        """Set the request ID"""
        self.request_id = request_id
        return self

    def build(self) -> UCXLSuccessResponse:
        """Build the success response"""
        message = self.message or self.code.default_message()
        details = self.details if self.details else None

        response = UCXLResponse(
            code=self.code,
            message=message,
            data=self.data,
            details=details,
            request_id=self.request_id,
            timestamp=datetime.now(timezone.utc),
        )
        return UCXLSuccessResponse(response=response)


# Convenience functions for common error patterns

def create_invalid_address_error(address: str, path: str = "/", request_id: Optional[str] = None) -> UCXLErrorResponse:
    """Create a standardized invalid address error"""
    builder = UCXLErrorBuilder(UCXLErrorCode.INVALID_ADDRESS)
    builder.field("address", address)
    builder.expected_format("ucxl://<agent>:<role>@<project>:<task>[/<temporal>/<path>]")
    builder.path(path)
    builder.cause("parse_error")
    if request_id:
        builder.request_id(request_id)
    return builder.build()


def create_not_found_error(resource_id: str, path: str = "/", request_id: Optional[str] = None) -> UCXLErrorResponse:
    """Create a standardized not found error"""
    builder = UCXLErrorBuilder(UCXLErrorCode.NOT_FOUND)
    builder.field("resource_id", resource_id)
    builder.path(path)
    if request_id:
        builder.request_id(request_id)
    return builder.build()


def create_missing_field_error(field: str, path: str = "/", request_id: Optional[str] = None) -> UCXLErrorResponse:
    """Create a standardized missing field error"""
    builder = UCXLErrorBuilder(UCXLErrorCode.MISSING_FIELD)
    builder.message(f"Required field '{field}' is missing")
    builder.field(field, None)
    builder.path(path)
    builder.cause("validation_error")
    if request_id:
        builder.request_id(request_id)
    return builder.build()


def create_success_response(data: Any = None, path: str = "/", request_id: Optional[str] = None) -> UCXLSuccessResponse:
    """Create a standardized success response"""
    builder = UCXLResponseBuilder(UCXLResponseCode.OK)
    if data is not None:
        builder.data(data)
    if request_id:
        builder.request_id(request_id)
    return builder.build()


def create_created_response(resource_id: str, location: str, path: str = "/", request_id: Optional[str] = None) -> UCXLSuccessResponse:
    """Create a standardized resource created response"""
    builder = UCXLResponseBuilder(UCXLResponseCode.CREATED)
    builder.data({"id": resource_id, "url": location})
    builder.detail("location", location)
    if request_id:
        builder.request_id(request_id)
    return builder.build()


# Example usage and testing
if __name__ == "__main__":
    # Example error response
    error = UCXLErrorBuilder(UCXLErrorCode.INVALID_ADDRESS) \
        .message("Test error message") \
        .field("address", "ucxl://invalid") \
        .expected_format("ucxl://<agent>:<role>@<project>:<task>") \
        .source("test-service") \
        .path("/test") \
        .cause("parse_error") \
        .build()
    
    print("Error Response JSON:")
    print(error.to_json())
    print()
    
    # Example success response
    success = UCXLResponseBuilder(UCXLResponseCode.OK) \
        .message("Test success") \
        .data({"test": "value"}) \
        .detail("info", "additional info") \
        .build()
    
    print("Success Response JSON:")
    print(success.to_json())
    print()
    
    # Test serialization round-trip
    error_json = error.to_json()
    error_restored = UCXLErrorResponse.from_json(error_json)
    print("Error serialization test:", error.error.code == error_restored.error.code)
    
    success_json = success.to_json()
    success_restored = UCXLSuccessResponse.from_json(success_json)
    print("Success serialization test:", success.response.code == success_restored.response.code)