package site.daydream.colleen.example.kotlin

import site.daydream.colleen.*
import site.daydream.colleen.middleware.RequestLog
import java.lang.reflect.Parameter
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*

/**
 * Custom Parameter Extractor Examples
 *
 * Features demonstrated:
 * - Creating custom extractors for domain-specific needs
 * - Parsing and validating complex types
 * - Composing multiple extraction strategies
 * - Error handling in custom extractors
 */

// ========================================================================
// 1. Date and Time Extractors
// ========================================================================

/**
 * Extracts and parses ISO date from query parameters
 * 
 * Example: ?date=2024-01-15
 */
class DateParam(value: LocalDate?) : ParamExtractor<LocalDate?>(value) {
    companion object : ExtractorFactory<DateParam> {
        override fun build(name: String, param: Parameter): (Context) -> DateParam {
            requireParamName("DateParam", name)
            
            return { ctx ->
                val dateStr = ctx.query(name)
                val parsed = dateStr?.let {
                    try {
                        LocalDate.parse(it, DateTimeFormatter.ISO_LOCAL_DATE)
                    } catch (e: Exception) {
                        throw TypeConversionFailed(it, "LocalDate", e)
                    }
                }
                DateParam(parsed)
            }
        }
    }
}

/**
 * Extracts and parses ISO datetime from query parameters
 * 
 * Example: ?timestamp=2024-01-15T10:30:00
 */
class DateTimeParam(value: LocalDateTime?) : ParamExtractor<LocalDateTime?>(value) {
    companion object : ExtractorFactory<DateTimeParam> {
        override fun build(name: String, param: Parameter): (Context) -> DateTimeParam {
            requireParamName("DateTimeParam", name)
            
            return { ctx ->
                val dateTimeStr = ctx.query(name)
                val parsed = dateTimeStr?.let {
                    try {
                        LocalDateTime.parse(it, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
                    } catch (e: Exception) {
                        throw TypeConversionFailed(it, "LocalDateTime", e)
                    }
                }
                DateTimeParam(parsed)
            }
        }
    }
}

// ========================================================================
// 2. Enum Extractor
// ========================================================================

enum class UserRole {
    ADMIN, USER, GUEST
}

enum class SortOrder {
    ASC, DESC
}

/**
 * Extracts and parses enum values from query parameters
 * Supports case-insensitive matching
 * 
 * Example: ?role=admin or ?role=ADMIN
 */
class EnumParam<T : Enum<T>>(value: T?) : ParamExtractor<T?>(value) {
    companion object : ExtractorFactory<EnumParam<*>> {
        override fun build(name: String, param: Parameter): (Context) -> EnumParam<*> {
            requireParamName("EnumParam", name)
            
            val innerType = param.unwrapGeneric()
            val enumClass = innerType.getRawClass() as Class<out Enum<*>>
            
            return { ctx ->
                val enumStr = ctx.query(name)
                val parsed = enumStr?.let {
                    try {
                        // Case-insensitive enum parsing
                        enumClass.enumConstants.firstOrNull { 
                            it.name.equals(enumStr, ignoreCase = true) 
                        } ?: throw IllegalArgumentException(
                            "Invalid value '$enumStr' for enum ${enumClass.simpleName}. " +
                            "Valid values: ${enumClass.enumConstants.joinToString { it.name }}"
                        )
                    } catch (e: Exception) {
                        throw TypeConversionFailed(it, enumClass.simpleName, e)
                    }
                }
                EnumParam(parsed)
            }
        }
    }
}

// ========================================================================
// 3. UUID Extractor
// ========================================================================

/**
 * Extracts and validates UUID from path parameters
 * 
 * Example: /users/550e8400-e29b-41d4-a716-446655440000
 */
class UuidParam(value: UUID?) : ParamExtractor<UUID?>(value) {
    companion object : ExtractorFactory<UuidParam> {
        override fun build(name: String, param: Parameter): (Context) -> UuidParam {
            requireParamName("UuidParam", name)
            
            return { ctx ->
                val uuidStr = ctx.pathParam(name)
                val parsed = uuidStr?.let {
                    try {
                        UUID.fromString(it)
                    } catch (e: IllegalArgumentException) {
                        throw TypeConversionFailed(it, "UUID", e)
                    }
                }
                UuidParam(parsed)
            }
        }
    }
}

// ========================================================================
// 4. Comma-Separated List Extractor
// ========================================================================

/**
 * Extracts comma-separated values from a single query parameter
 * 
 * Example: ?tags=kotlin,java,spring -> ["kotlin", "java", "spring"]
 */
class CsvParam(value: List<String>?) : ParamExtractor<List<String>?>(value) {
    companion object : ExtractorFactory<CsvParam> {
        override fun build(name: String, param: Parameter): (Context) -> CsvParam {
            requireParamName("CsvParam", name)
            
            return { ctx ->
                val csvStr = ctx.query(name)
                val list = csvStr?.split(",")
                    ?.map { it.trim() }
                    ?.filter { it.isNotEmpty() }
                CsvParam(list)
            }
        }
    }
}

// ========================================================================
// 5. Pagination Extractor
// ========================================================================

/**
 * Pagination parameters with validation
 */
data class Pagination(
    val page: Int = 1,
    val size: Int = 20,
    val offset: Int = (page - 1) * size
) {
    init {
        require(page > 0) { "Page must be greater than 0" }
        require(size in 1..100) { "Size must be between 1 and 100" }
    }
}

/**
 * Extracts pagination parameters from query string
 * 
 * Example: ?page=2&size=50
 */
class PageParam(value: Pagination) : ParamExtractor<Pagination>(value) {
    companion object : ExtractorFactory<PageParam> {
        override fun build(name: String, param: Parameter): (Context) -> PageParam {
            return { ctx ->
                val page = ctx.query("page")?.toIntOrNull() ?: 1
                val size = ctx.query("size")?.toIntOrNull() ?: 20
                
                try {
                    PageParam(Pagination(page, size))
                } catch (e: IllegalArgumentException) {
                    throw ParameterNotFound("PageParam", e.message ?: "Invalid pagination")
                }
            }
        }
    }
}

// ========================================================================
// 6. IP Address Extractor
// ========================================================================

/**
 * Extracts client IP address considering X-Forwarded-For header
 */
class IpAddress(value: String) : ParamExtractor<String>(value) {
    companion object : ExtractorFactory<IpAddress> {
        override fun build(name: String, param: Parameter): (Context) -> IpAddress {
            return { ctx ->
                val ip = ctx.header("X-Forwarded-For")
                    ?.split(",")
                    ?.firstOrNull()
                    ?.trim()
                    ?: ctx.header("X-Real-IP")
                    ?: ctx.request.ip
                    ?: "unknown"
                
                IpAddress(ip)
            }
        }
    }
}

// ========================================================================
// 7. User Agent Extractor
// ========================================================================

/**
 * Parsed user agent information
 */
data class UserAgentInfo(
    val raw: String,
    val isMobile: Boolean,
    val browser: String?,
    val os: String?
)

/**
 * Extracts and parses User-Agent header
 */
class UserAgent(value: UserAgentInfo) : ParamExtractor<UserAgentInfo>(value) {
    companion object : ExtractorFactory<UserAgent> {
        override fun build(name: String, param: Parameter): (Context) -> UserAgent {
            return { ctx ->
                val ua = ctx.header("User-Agent") ?: "Unknown"
                
                val info = UserAgentInfo(
                    raw = ua,
                    isMobile = ua.contains("Mobile", ignoreCase = true) ||
                              ua.contains("Android", ignoreCase = true),
                    browser = when {
                        ua.contains("Chrome") -> "Chrome"
                        ua.contains("Firefox") -> "Firefox"
                        ua.contains("Safari") -> "Safari"
                        ua.contains("Edge") -> "Edge"
                        else -> null
                    },
                    os = when {
                        ua.contains("Windows") -> "Windows"
                        ua.contains("Mac OS") -> "macOS"
                        ua.contains("Linux") -> "Linux"
                        ua.contains("Android") -> "Android"
                        ua.contains("iOS") -> "iOS"
                        else -> null
                    }
                )
                
                UserAgent(info)
            }
        }
    }
}

// ========================================================================
// 8. Range Extractor
// ========================================================================

/**
 * Numeric range with validation
 */
data class NumericRange(
    val min: Int,
    val max: Int
) {
    init {
        require(min <= max) { "Min ($min) must be less than or equal to max ($max)" }
        require(max - min <= 1000) { "Range too large (max 1000)" }
    }
}

/**
 * Extracts numeric range from query parameters
 * 
 * Example: ?min=10&max=100
 */
class RangeParam(value: NumericRange?) : ParamExtractor<NumericRange?>(value) {
    companion object : ExtractorFactory<RangeParam> {
        override fun build(name: String, param: Parameter): (Context) -> RangeParam {
            return { ctx ->
                val min = ctx.query("min")?.toIntOrNull()
                val max = ctx.query("max")?.toIntOrNull()
                
                val range = if (min != null && max != null) {
                    try {
                        NumericRange(min, max)
                    } catch (e: IllegalArgumentException) {
                        throw ParameterNotFound("RangeParam", e.message ?: "Invalid range")
                    }
                } else null
                
                RangeParam(range)
            }
        }
    }
}

// ========================================================================
// 9. Base64 Decoded Extractor
// ========================================================================

/**
 * Extracts and decodes Base64-encoded data from query parameter
 * 
 * Example: ?data=SGVsbG8gV29ybGQ=
 */
class Base64Param(value: String?) : ParamExtractor<String?>(value) {
    companion object : ExtractorFactory<Base64Param> {
        override fun build(name: String, param: Parameter): (Context) -> Base64Param {
            requireParamName("Base64Param", name)
            
            return { ctx ->
                val encoded = ctx.query(name)
                val decoded = encoded?.let {
                    try {
                        String(Base64.getDecoder().decode(it))
                    } catch (e: IllegalArgumentException) {
                        throw TypeConversionFailed(it, "Base64", e)
                    }
                }
                Base64Param(decoded)
            }
        }
    }
}

// ========================================================================
// Example Application
// ========================================================================

fun customExtractorsDemo() {
    val app = Colleen()
    
    app.use(RequestLog())

    // 1. Date and Time
    app.get("/events", cx(::getEvents))
    
    // 2. Enum
    app.get("/users/filter", cx(::filterUsers))
    
    // 3. UUID
    app.get("/resources/{id}", cx(::getResource))
    
    // 4. CSV
    app.get("/search", cx(::searchByTags))
    
    // 5. Pagination
    app.get("/posts", cx(::getPosts))
    
    // 6. IP Address
    app.get("/visitor-info", cx(::getVisitorInfo))
    
    // 7. User Agent
    app.get("/device-info", cx(::getDeviceInfo))
    
    // 8. Range
    app.get("/products", cx(::getProductsByPrice))
    
    // 9. Base64
    app.get("/decode", cx(::decodeData))
    
    // 10. Combined example
    app.get("/reports", cx(::getReports))

    app.get("/") { ctx ->
        ctx.html("""
            <!DOCTYPE html>
            <html>
            <head>
                <title>Custom Parameter Extractors</title>
                <style>
                    body { font-family: Arial, sans-serif; max-width: 1000px; margin: 40px auto; padding: 0 20px; }
                    h1 { color: #333; }
                    h2 { color: #666; margin-top: 30px; }
                    .example { background: #f5f5f5; padding: 15px; margin: 10px 0; border-radius: 5px; }
                    code { background: #e0e0e0; padding: 2px 6px; border-radius: 3px; }
                    a { color: #0066cc; text-decoration: none; }
                    a:hover { text-decoration: underline; }
                </style>
            </head>
            <body>
                <h1>Custom Parameter Extractor Examples</h1>
                
                <h2>1. Date and Time Extractors</h2>
                <div class="example">
                    <a href="/events?start=2024-01-01&end=2024-12-31">/events?start=2024-01-01&end=2024-12-31</a>
                    <p>Extracts and validates ISO date formats</p>
                </div>
                
                <h2>2. Enum Extractor</h2>
                <div class="example">
                    <a href="/users/filter?role=admin&sort=desc">/users/filter?role=admin&sort=desc</a>
                    <p>Case-insensitive enum parsing with validation</p>
                </div>
                
                <h2>3. UUID Extractor</h2>
                <div class="example">
                    <a href="/resources/550e8400-e29b-41d4-a716-446655440000">/resources/550e8400-e29b-41d4-a716-446655440000</a>
                    <p>Validates and parses UUID format</p>
                </div>
                
                <h2>4. CSV List Extractor</h2>
                <div class="example">
                    <a href="/search?tags=kotlin,web,framework">/search?tags=kotlin,web,framework</a>
                    <p>Parses comma-separated values into a list</p>
                </div>
                
                <h2>5. Pagination Extractor</h2>
                <div class="example">
                    <a href="/posts?page=2&size=50">/posts?page=2&size=50</a>
                    <p>Validates and calculates pagination with offset</p>
                </div>
                
                <h2>6. IP Address Extractor</h2>
                <div class="example">
                    <a href="/visitor-info">/visitor-info</a>
                    <p>Extracts client IP from headers and request</p>
                </div>
                
                <h2>7. User Agent Extractor</h2>
                <div class="example">
                    <a href="/device-info">/device-info</a>
                    <p>Parses browser and OS information</p>
                </div>
                
                <h2>8. Range Extractor</h2>
                <div class="example">
                    <a href="/products?min=100&max=500">/products?min=100&max=500</a>
                    <p>Validates numeric ranges with constraints</p>
                </div>
                
                <h2>9. Base64 Decoder</h2>
                <div class="example">
                    <a href="/decode?data=SGVsbG8gV29ybGQ=">/decode?data=SGVsbG8gV29ybGQ=</a>
                    <p>Decodes Base64-encoded data (Hello World)</p>
                </div>
                
                <h2>10. Combined Example</h2>
                <div class="example">
                    <a href="/reports?start=2024-01-01&end=2024-03-31&role=admin&page=1&size=25">
                        /reports?start=2024-01-01&end=2024-03-31&role=admin&page=1&size=25
                    </a>
                    <p>Uses multiple custom extractors together</p>
                </div>
            </body>
            </html>
        """.trimIndent())
    }

    app.listen(8090) {
        println("✅ Custom extractors demo running on http://localhost:8090")
    }
}

// Handler functions

fun getEvents(
    start: DateParam,
    end: DateParam
): Map<String, Any?> {
    return mapOf(
        "start" to start.value,
        "end" to end.value,
        "events" to listOf("Event 1", "Event 2", "Event 3")
    )
}

fun filterUsers(
    role: EnumParam<UserRole>,
    sort: EnumParam<SortOrder> = EnumParam(SortOrder.ASC)
): Map<String, Any?> {
    return mapOf(
        "role" to role.value,
        "sortOrder" to sort.value,
        "users" to listOf("User 1", "User 2")
    )
}

fun getResource(id: UuidParam): Map<String, Any?> {
    return mapOf(
        "id" to id.value,
        "type" to "resource",
        "found" to true
    )
}

fun searchByTags(tags: CsvParam): Map<String, Any?> {
    return mapOf(
        "tags" to tags.value,
        "count" to (tags.value?.size ?: 0),
        "results" to listOf("Result 1", "Result 2")
    )
}

fun getPosts(pagination: PageParam): Map<String, Any> {
    val page = pagination.value
    return mapOf(
        "page" to page.page,
        "size" to page.size,
        "offset" to page.offset,
        "total" to 100,
        "posts" to (1..page.size).map { "Post ${page.offset + it}" }
    )
}

fun getVisitorInfo(ip: IpAddress): Map<String, Any> {
    return mapOf(
        "ip" to ip.value,
        "timestamp" to System.currentTimeMillis()
    )
}

fun getDeviceInfo(userAgent: UserAgent): Map<String, Any> {
    val info = userAgent.value
    return mapOf(
        "browser" to info.browser,
        "os" to info.os,
        "isMobile" to info.isMobile,
        "raw" to info.raw
    )
}

fun getProductsByPrice(range: RangeParam): Map<String, Any?> {
    return mapOf(
        "minPrice" to range.value?.min,
        "maxPrice" to range.value?.max,
        "products" to listOf("Product A", "Product B")
    )
}

fun decodeData(data: Base64Param): Map<String, Any?> {
    return mapOf(
        "decoded" to data.value
    )
}

fun getReports(
    start: DateParam,
    end: DateParam,
    role: EnumParam<UserRole>?,
    pagination: PageParam
): Map<String, Any?> {
    return mapOf(
        "period" to mapOf(
            "start" to start.value,
            "end" to end.value
        ),
        "filterRole" to role?.value,
        "pagination" to mapOf(
            "page" to pagination.value.page,
            "size" to pagination.value.size
        ),
        "reports" to listOf("Report 1", "Report 2", "Report 3")
    )
}

// ========================================================================
// Main
// ========================================================================

fun main() {
    customExtractorsDemo()
}