package com.horrortv.app.domain.usecase

import android.util.Log
import com.horrortv.app.domain.model.VideoSource
import com.horrortv.app.domain.model.VideoType
import com.horrortv.app.domain.model.SubtitleTrack
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.jsoup.Jsoup
import org.json.JSONArray
import java.net.HttpURLConnection
import java.net.URL
import java.util.zip.GZIPInputStream

class VideoExtractor {
    
    companion object {
        private const val TAG = "VideoExtractor"
        private const val PLAYIMDB_BASE = "https://playimdb.com/title/"
        private const val STREAMIMDB_BASE = "https://streamimdb.me/embed/"
        private const val TIMEOUT_MS = 15000
        private val ALLOWED_LANGUAGES = setOf("en", "es", "english", "spanish", "eng", "spa")
        private val ALLOWED_HOSTS = setOf(
            "streamimdb.me", "www.streamimdb.me",
            "cloudnestra.com", "www.cloudnestra.com",
            "playimdb.com", "www.playimdb.com"
        )
        private val IMDB_ID_PATTERN = Regex("""^tt\d{7,}${'$'}""")

        private fun isAllowedHost(url: String): Boolean {
            return try { ALLOWED_HOSTS.contains(URL(url).host) } catch (_: Exception) { false }
        }
    }
    
    suspend fun extractVideoSource(imdbId: String): Result<VideoSource> = withContext(Dispatchers.IO) {
        try {
            if (!IMDB_ID_PATTERN.matches(imdbId)) {
                return@withContext Result.failure(VideoExtractionException("Invalid IMDb ID: $imdbId"))
            }
            Log.d(TAG, "Extracting video for: $imdbId")
            
            val embedUrl = "$STREAMIMDB_BASE$imdbId"
            Log.d(TAG, "Fetching embed: $embedUrl")
            
            val html = fetchHtml(embedUrl)
            Log.d(TAG, "HTML length: ${html.length}")
            
            var doc = Jsoup.parse(html, embedUrl)
            var finalHtml = html
            
            val iframeUrl = doc.selectFirst("iframe")?.absUrl("src")
            if (!iframeUrl.isNullOrEmpty()) {
                Log.d(TAG, "Found iframe redirect: $iframeUrl")
                try {
                    val iframeHtml = fetchHtml(iframeUrl)
                    Log.d(TAG, "Iframe HTML length: ${iframeHtml.length}")
                    
                    val prorcpPattern = Regex("['\"]?src['\"]?\\s*:\\s*['\"]([^'\"]+/prorcp/[^'\"]+)['\"]")
                    val prorcpMatch = prorcpPattern.find(iframeHtml)
                    if (prorcpMatch != null) {
                        val innerUrl = prorcpMatch.groupValues[1]
                        val fullInnerUrl = if (innerUrl.startsWith("/")) {
                            "https://cloudnestra.com$innerUrl"
                        } else if (innerUrl.startsWith("//")) {
                            "https:$innerUrl"
                        } else {
                            innerUrl
                        }
                        Log.d(TAG, "Found prorcp URL: $fullInnerUrl")
                        try {
                            val innerHtml = fetchHtml(fullInnerUrl)
                            Log.d(TAG, "Prorcp HTML length: ${innerHtml.length}")
                            doc = Jsoup.parse(innerHtml, fullInnerUrl)
                            finalHtml = innerHtml
                        } catch (e: Exception) {
                            Log.w(TAG, "Failed prorcp fetch: ${e.message}")
                        }
                    } else {
                        Log.d(TAG, "No prorcp pattern found, searching for /prorcp/ directly")
                        val directPattern = Regex("/prorcp/[A-Za-z0-9+/=]+")
                        val directMatch = directPattern.find(iframeHtml)
                        if (directMatch != null) {
                            val innerUrl = directMatch.value
                            val fullInnerUrl = "https://cloudnestra.com$innerUrl"
                            Log.d(TAG, "Found prorcp direct: $fullInnerUrl")
                            try {
                                val innerHtml = fetchHtml(fullInnerUrl)
                                Log.d(TAG, "Prorcp direct HTML length: ${innerHtml.length}")
                                doc = Jsoup.parse(innerHtml, fullInnerUrl)
                                finalHtml = innerHtml
                            } catch (e: Exception) {
                                Log.w(TAG, "Failed prorcp direct fetch: ${e.message}")
                            }
                        }
                    }
                } catch (e: Exception) {
                    Log.w(TAG, "Failed to fetch iframe: ${e.message}")
                }
            }
            
            val videoUrl = extractVideoUrl(doc)
            if (videoUrl == null) {
                Log.w(TAG, "No video URL found in HTML")
                return@withContext Result.failure(VideoExtractionException("No video URL found"))
            }
            
            Log.d(TAG, "Video URL found: $videoUrl")
            
            val knownDomains = listOf(
                "neonhorizonworkshops.com",
                "wanderlynest.com",
                "orchidpixelgardens.com",
                "cloudnestra.com"
            )
            
            val finalVideoUrl = if (videoUrl.contains("{v")) {
                val basePattern = Regex("""https://tmstr3\.\{v\d+\}/(.+)""")
                val pathMatch = basePattern.find(videoUrl)
                if (pathMatch != null) {
                    val path = pathMatch.groupValues[1]
                    val testDomain = knownDomains.first()
                    val constructedUrl = "https://tmstr1.$testDomain/$path"
                    Log.d(TAG, "Constructed URL from known domain: $constructedUrl")
                    constructedUrl
                } else {
                    videoUrl.replace(Regex("\\{v\\d+\\}"), knownDomains.first())
                }
            } else {
                videoUrl
            }
            
            Log.d(TAG, "Final video URL: $finalVideoUrl")
            
            val subtitles = extractSubtitles(doc, finalHtml, finalVideoUrl).toMutableList()
            
            val openSubtitles = fetchSubtitlesFromOpenSubtitles(imdbId)
            for (sub in openSubtitles) {
                if (!subtitles.any { it.url == sub.url }) {
                    subtitles.add(sub)
                }
            }

            // Deduplicate by language - keep first (priority: embed > HLS > OpenSubtitles)
            val seen = mutableSetOf<String>()
            val dedupedTracks = subtitles.filter { seen.add(it.language.lowercase().trim()) }

            val filteredSubtitles = dedupedTracks.filter { isAllowedLanguage(it.language.lowercase(), it.label.lowercase()) }
            Log.d(TAG, "Subtitles found: ${filteredSubtitles.size}")
            
            val videoType = determineVideoType(finalVideoUrl)
            Log.d(TAG, "Video type: $videoType")
            
            Result.success(
                VideoSource(
                    videoUrl = finalVideoUrl,
                    videoType = videoType,
                    subtitleTracks = filteredSubtitles,
                    title = imdbId
                )
            )
        } catch (e: CancellationException) {
            throw e
        } catch (e: Exception) {
            Log.e(TAG, "Error extracting video", e)
            Result.failure(VideoExtractionException(e.message ?: "Unknown error", e))
        }
    }
    
    private fun fetchHtml(url: String): String {
        if (!isAllowedHost(url)) {
            throw VideoExtractionException("Host not allowed: $url")
        }
        return doHttpFetch(url)
    }

    private fun fetchManifest(url: String): String {
        return doHttpFetch(url)
    }

    private fun fetchOpenSubtitles(url: String): String {
        return doHttpFetch(url, mapOf("X-User-Agent" to "HorrorTV/1.0"))
    }

    private fun doHttpFetch(url: String, extraHeaders: Map<String, String> = emptyMap()): String {
        var connection: HttpURLConnection? = null
        try {
            connection = (URL(url).openConnection() as HttpURLConnection).apply {
                requestMethod = "GET"
                connectTimeout = TIMEOUT_MS
                readTimeout = TIMEOUT_MS
                setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36")
                setRequestProperty("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
                setRequestProperty("Accept-Language", "en-US,en;q=0.5")
                setRequestProperty("Accept-Encoding", "gzip, deflate")
                setRequestProperty("Referer", PLAYIMDB_BASE)
                extraHeaders.forEach { (key, value) -> setRequestProperty(key, value) }
                instanceFollowRedirects = true
            }

            val responseCode = connection.responseCode
            Log.d(TAG, "Response code: $responseCode for $url")

            if (responseCode != HttpURLConnection.HTTP_OK) {
                throw VideoExtractionException("HTTP error: $responseCode")
            }

            val inputStream = connection.inputStream.let { stream ->
                if (connection.contentEncoding?.contains("gzip", ignoreCase = true) == true) {
                    GZIPInputStream(stream)
                } else {
                    stream
                }
            }

            return inputStream.bufferedReader().use { it.readText() }
        } finally {
            connection?.disconnect()
        }
    }
    
    private fun extractVideoUrl(doc: org.jsoup.nodes.Document): String? {
        val videoElement = doc.selectFirst("video")
        if (videoElement != null) {
            val src = videoElement.absUrl("src")
            if (src.isNotEmpty()) {
                Log.d(TAG, "Found video[src]: $src")
                return src
            }
            
            val sourceElements = videoElement.select("source")
            for (source in sourceElements) {
                val srcAttr = source.absUrl("src")
                if (srcAttr.isNotEmpty() && isValidVideoUrl(srcAttr)) {
                    Log.d(TAG, "Found source[src]: $srcAttr")
                    return srcAttr
                }
            }
        }
        
        val scripts = doc.select("script")
        for (script in scripts) {
            val scriptContent = script.html()
            val urls = extractUrlsFromScript(scriptContent)
            for (url in urls) {
                if (isValidVideoUrl(url)) {
                    Log.d(TAG, "Found URL in script: $url")
                    return url
                }
            }
        }
        
        val allElements = doc.select("*[src], *[href], *[data-src], *[data-url]")
        for (elem in allElements) {
            val possibleUrl = elem.attr("src")
                .ifEmpty { elem.attr("href") }
                .ifEmpty { elem.attr("data-src") }
                .ifEmpty { elem.attr("data-url") }
            
            if (possibleUrl.isNotEmpty() && isValidVideoUrl(possibleUrl)) {
                Log.d(TAG, "Found URL in element: $possibleUrl")
                return possibleUrl
            }
        }
        
        val htmlText = doc.html()
        val m3u8Pattern = Regex("https?://[^\"'<>\\s]+\\.m3u8[^\"'<>\\s]*")
        val m3u8Match = m3u8Pattern.find(htmlText)
        if (m3u8Match != null) {
            Log.d(TAG, "Found .m3u8 via regex: ${m3u8Match.value}")
            return m3u8Match.value
        }
        
        val mp4Pattern = Regex("https?://[^\"'<>\\s]+\\.mp4[^\"'<>\\s]*")
        val mp4Match = mp4Pattern.find(htmlText)
        if (mp4Match != null) {
            Log.d(TAG, "Found .mp4 via regex: ${mp4Match.value}")
            return mp4Match.value
        }
        
        return null
    }
    
    private fun extractUrlsFromScript(scriptContent: String): List<String> {
        val urlPattern = Regex("https?://[^\"'<>\\s]+")
        return urlPattern.findAll(scriptContent)
            .map { it.value }
            .filter { isValidVideoUrl(it) }
            .toList()
    }
    
    private fun isValidVideoUrl(url: String): Boolean {
        val lower = url.lowercase()
        return (lower.contains(".m3u8") || lower.contains(".mp4") || lower.contains(".mkv") || lower.contains(".mpd")) &&
               (url.startsWith("http://") || url.startsWith("https://")) &&
               !lower.contains(".js") && !lower.contains(".css") && !lower.contains(".html")
    }
    
    private fun determineVideoType(url: String): VideoType {
        val path = try { URL(url).path.lowercase() } catch (_: Exception) { url.lowercase() }
        return when {
            path.endsWith(".m3u8") -> VideoType.HLS
            path.endsWith(".mp4") -> VideoType.MP4
            path.endsWith(".mpd") -> VideoType.DASH
            else -> VideoType.UNKNOWN
        }
    }
    
    private fun extractSubtitles(doc: org.jsoup.nodes.Document, htmlContent: String, videoUrl: String?): List<SubtitleTrack> {
        val tracks = mutableListOf<SubtitleTrack>()
        
        val trackElements = doc.select("track")
        for (track in trackElements) {
            val src = track.absUrl("src")
            val srclang = track.attr("srclang").lowercase()
            val label = track.attr("label").ifEmpty { srclang }
            val isDefault = track.hasAttr("default")
            
            if (src.isNotEmpty() && isAllowedLanguage(srclang, label)) {
                tracks.add(
                    SubtitleTrack(
                        url = src,
                        language = normalizeLanguage(srclang),
                        label = label,
                        isDefault = isDefault,
                        mimeType = determineMimeType(src)
                    )
                )
                Log.d(TAG, "Found track element subtitle: $srclang - $src")
            }
        }
        
        val subtitlePatterns = listOf(
            Regex("\"sub\"\\s*:\\s*\"([^\"]+)\""),
            Regex("\"subtitle\"\\s*:\\s*\"([^\"]+)\""),
            Regex("\"captions\"\\s*:\\s*\"([^\"]+)\""),
            Regex("\"file\"\\s*:\\s*\"([^\"]+\\.vtt[^\"]*)\""),
            Regex("\"file\"\\s*:\\s*\"([^\"]+\\.srt[^\"]*)\""),
            Regex("'sub'\\s*:\\s*'([^']+)'"),
            Regex("'subtitle'\\s*:\\s*'([^']+)'"),
            Regex("'captions'\\s*:\\s*'([^']+)'")
        )
        
        for (pattern in subtitlePatterns) {
            pattern.findAll(htmlContent).forEach { match ->
                val url = match.groupValues[1]
                if (url.isNotEmpty() && !tracks.any { it.url == url }) {
                    val cleanUrl = cleanSubtitleUrl(url)
                    if (cleanUrl.isNotEmpty() && isSubtitleUrl(cleanUrl)) {
                        tracks.add(
                            SubtitleTrack(
                                url = cleanUrl,
                                language = "en",
                                label = "English",
                                isDefault = false,
                                mimeType = determineMimeType(cleanUrl)
                            )
                        )
                        Log.d(TAG, "Found JS pattern subtitle: $cleanUrl")
                    }
                }
            }
        }
        
        val vttPattern = Regex("https?://[^\"'<>\\s]+\\.vtt[^\"'<>\\s]*")
        vttPattern.findAll(htmlContent).forEach { match ->
            val url = match.value
            if (!tracks.any { it.url == url }) {
                tracks.add(
                    SubtitleTrack(
                        url = url,
                        language = "en",
                        label = "English",
                        isDefault = false,
                        mimeType = "text/vtt"
                    )
                )
                Log.d(TAG, "Found VTT URL: $url")
            }
        }
        
        val srtPattern = Regex("https?://[^\"'<>\\s]+\\.srt[^\"'<>\\s]*")
        srtPattern.findAll(htmlContent).forEach { match ->
            val url = match.value
            if (!tracks.any { it.url == url }) {
                tracks.add(
                    SubtitleTrack(
                        url = url,
                        language = "en",
                        label = "English",
                        isDefault = false,
                        mimeType = "application/x-subrip"
                    )
                )
                Log.d(TAG, "Found SRT URL: $url")
            }
        }
        
        val jsonArrayPattern = Regex("\\[[\\s\\S]*?\"url\"[\\s\\S]*?\\]")
        jsonArrayPattern.findAll(htmlContent).forEach { match ->
            try {
                val jsonArray = match.value
                val urlPattern = Regex("\"url\"\\s*:\\s*\"([^\"]+)\"")
                val langPattern = Regex("\"lang\"\\s*:\\s*\"([^\"]+)\"")
                val labelPattern = Regex("\"label\"\\s*:\\s*\"([^\"]+)\"")
                
                urlPattern.findAll(jsonArray).forEach { urlMatch ->
                    val subUrl = urlMatch.groupValues[1]
                    val langMatch = langPattern.find(jsonArray)
                    val labelMatch = labelPattern.find(jsonArray)
                    
                    val lang = langMatch?.groupValues?.get(1)?.lowercase() ?: "en"
                    val subLabel = labelMatch?.groupValues?.get(1) ?: lang
                    
                    if (subUrl.isNotEmpty() && !tracks.any { it.url == subUrl } && isAllowedLanguage(lang, subLabel)) {
                        tracks.add(
                            SubtitleTrack(
                                url = subUrl,
                                language = normalizeLanguage(lang),
                                label = subLabel,
                                isDefault = false,
                                mimeType = determineMimeType(subUrl)
                            )
                        )
                        Log.d(TAG, "Found JSON array subtitle: $lang - $subUrl")
                    }
                }
            } catch (e: Exception) {
                Log.w(TAG, "Error parsing subtitle JSON array: ${e.message}")
            }
        }
        
        val jwplayerPattern = Regex("\\{[^}]*file:\"([^\"]+)\"[^}]*label:\"([^\"]+)\"[^}]*kind:\"captions\"[^}]*\\}")
        jwplayerPattern.findAll(htmlContent).forEach { match ->
            val url = match.groupValues[1]
            val label = match.groupValues[2].lowercase()
            
            if (url.isNotEmpty() && !tracks.any { it.url == url } && isAllowedLanguage(label, label)) {
                tracks.add(
                    SubtitleTrack(
                        url = url,
                        language = normalizeLanguage(label),
                        label = match.groupValues[2],
                        isDefault = false,
                        mimeType = determineMimeType(url)
                    )
                )
                Log.d(TAG, "Found JWPlayer subtitle: $label - $url")
            }
        }
        
        if (videoUrl != null && videoUrl.contains(".m3u8")) {
            val hlsSubtitles = extractHlsSubtitles(videoUrl)
            for (sub in hlsSubtitles) {
                if (!tracks.any { it.url == sub.url }) {
                    tracks.add(sub)
                    Log.d(TAG, "Found HLS manifest subtitle: ${sub.language} - ${sub.url}")
                }
            }
        }
        
        return tracks.filter { isAllowedLanguage(it.language.lowercase(), it.label.lowercase()) }
    }
    
    private fun extractHlsSubtitles(videoUrl: String): List<SubtitleTrack> {
        val tracks = mutableListOf<SubtitleTrack>()
        
        try {
            val manifestContent = fetchManifest(videoUrl)
            
            val subtitleMediaPattern = Regex(
                "#EXT-X-MEDIA:TYPE=SUBTITLES[^\\n]*LANGUAGE=\"([^\"]+)\"[^\\n]*NAME=\"([^\"]+)\"[^\\n]*(?:DEFAULT=YES)?[^\\n]*URI=\"([^\"]+)\""
            )
            subtitleMediaPattern.findAll(manifestContent).forEach { match ->
                val lang = match.groupValues[1].lowercase()
                val name = match.groupValues[2]
                val uri = match.groupValues[3]
                
                val fullUrl = if (uri.startsWith("http")) {
                    uri
                } else {
                    val baseUrl = videoUrl.substringBeforeLast("/") + "/"
                    baseUrl + uri
                }
                
                if (fullUrl.isNotEmpty() && isAllowedLanguage(lang, name)) {
                    tracks.add(
                        SubtitleTrack(
                            url = fullUrl,
                            language = normalizeLanguage(lang),
                            label = name,
                            isDefault = match.value.contains("DEFAULT=YES", ignoreCase = true),
                            mimeType = "text/vtt"
                        )
                    )
                }
            }
            
            val simpleSubtitlePattern = Regex("#EXT-X-MEDIA:TYPE=SUBTITLES[^\n]*")
            simpleSubtitlePattern.findAll(manifestContent).forEach { match ->
                val line = match.value
                val langMatch = Regex("LANGUAGE=\"([^\"]+)\"").find(line)
                val nameMatch = Regex("NAME=\"([^\"]+)\"").find(line)
                val uriMatch = Regex("URI=\"([^\"]+)\"").find(line)
                
                val lang = langMatch?.groupValues?.get(1)?.lowercase() ?: "en"
                val name = nameMatch?.groupValues?.get(1) ?: lang
                val uri = uriMatch?.groupValues?.get(1)
                
                if (uri != null) {
                    val fullUrl = if (uri.startsWith("http")) {
                        uri
                    } else {
                        val baseUrl = videoUrl.substringBeforeLast("/") + "/"
                        baseUrl + uri
                    }
                    
                    if (fullUrl.isNotEmpty() && isAllowedLanguage(lang, name) && !tracks.any { it.url == fullUrl }) {
                        tracks.add(
                            SubtitleTrack(
                                url = fullUrl,
                                language = normalizeLanguage(lang),
                                label = name,
                                isDefault = line.contains("DEFAULT=YES", true),
                                mimeType = "text/vtt"
                            )
                        )
                    }
                }
            }
        } catch (e: Exception) {
            Log.w(TAG, "Failed to extract HLS subtitles: ${e.message}")
        }
        
        return tracks
    }
    
    fun fetchSubtitlesFromOpenSubtitles(imdbId: String): List<SubtitleTrack> {
        val tracks = mutableListOf<SubtitleTrack>()
        val numericId = imdbId.removePrefix("tt").trim()
        if (numericId.isEmpty()) return tracks

        val languages = listOf(
            "eng" to "en",
            "spa" to "es"
        )

        for ((subLangCode, normalizedLang) in languages) {
            val url = "https://rest.opensubtitles.org/search/imdbid-$numericId/sublanguageid-$subLangCode"
            try {
                val response = fetchOpenSubtitles(url)
                val jsonArray = JSONArray(response)
                for (i in 0 until jsonArray.length()) {
                    val obj = jsonArray.getJSONObject(i)
                    val downloadLink = obj.optString("SubDownloadLink", "")
                    val subFormat = obj.optString("SubFormat", "srt")
                    val languageName = obj.optString("LanguageName", normalizedLang)

                    val isHearingImpaired = obj.optString("SubHearingImpaired", "0") == "1"
                    if (isHearingImpaired) continue

                    if (downloadLink.isNotEmpty() && tracks.none { it.language == normalizedLang }) {
                        tracks.add(
                            SubtitleTrack(
                                url = downloadLink,
                                language = normalizedLang,
                                label = languageName,
                                isDefault = false,
                                mimeType = if (subFormat.equals("vtt", ignoreCase = true)) "text/vtt" else "application/x-subrip"
                            )
                        )
                        Log.d(TAG, "OpenSubtitles track: $normalizedLang - $downloadLink")
                    }
                }
            } catch (e: Exception) {
                Log.w(TAG, "Failed to fetch OpenSubtitles for $subLangCode: ${e.message}")
            }
        }

        return tracks
    }

    private fun isAllowedLanguage(lang: String, label: String): Boolean {
        val normalizedLang = lang.lowercase().trim()
        val normalizedLabel = label.lowercase().trim()
        return normalizedLang == "en" || normalizedLang == "es" ||
               normalizedLang == "eng" || normalizedLang == "spa" ||
               normalizedLang.startsWith("english") || normalizedLang.startsWith("spanish") ||
               normalizedLabel.contains("english") || normalizedLabel.contains("spanish") ||
               normalizedLabel.contains("español")
    }
    
    private fun normalizeLanguage(lang: String): String {
        val normalized = lang.lowercase().trim()
        return when {
            normalized.contains("english") || normalized.contains("eng") -> "en"
            normalized.contains("spanish") || normalized.contains("spa") -> "es"
            normalized == "en" -> "en"
            normalized == "es" -> "es"
            else -> normalized.take(2)
        }
    }
    
    private fun isSubtitleUrl(url: String): Boolean {
        return url.contains(".vtt") || url.contains(".srt") || 
               url.contains("subtitle") || url.contains("caption") ||
               url.contains("sub")
    }
    
    private fun cleanSubtitleUrl(url: String, baseUrl: String = ""): String {
        val clean = url.trim().removeSurrounding("\"").removeSurrounding("'").trim()
        if (clean.isEmpty()) return ""
        return when {
            clean.startsWith("http://") || clean.startsWith("https://") -> clean
            clean.startsWith("/") -> {
                try {
                    val base = URL(baseUrl.ifEmpty { "https://cloudnestra.com" })
                    URL(base, clean).toString()
                } catch (_: Exception) { "https://cloudnestra.com$clean" }
            }
            else -> {
                try {
                    val base = URL(baseUrl.ifEmpty { "https://cloudnestra.com/" })
                    URL(base, clean).toString()
                } catch (_: Exception) { "" }
            }
        }
    }
    
    private fun determineMimeType(url: String): String {
        return when {
            url.contains(".srt", ignoreCase = true) -> "application/x-subrip"
            url.contains(".vtt", ignoreCase = true) -> "text/vtt"
            else -> "text/vtt"
        }
    }
}

class VideoExtractionException(message: String, cause: Throwable? = null) : Exception(message, cause)