package com.swoosh.microblog.data

import android.content.Context
import android.content.SharedPreferences
import com.swoosh.microblog.data.model.GhostAccount
import org.junit.Assert.*
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.RuntimeEnvironment
import org.robolectric.annotation.Config

@RunWith(RobolectricTestRunner::class)
@Config(sdk = [28], application = android.app.Application::class)
class AccountManagerTest {

    private lateinit var prefs: SharedPreferences
    private lateinit var accountManager: AccountManager

    @Before
    fun setUp() {
        val context = RuntimeEnvironment.getApplication()
        prefs = context.getSharedPreferences("test_prefs", Context.MODE_PRIVATE)
        prefs.edit().clear().apply()
        accountManager = AccountManager(prefs)
    }

    // --- CRUD: Add Account ---

    @Test
    fun `addAccount creates account with correct fields`() {
        val result = accountManager.addAccount("Personal Blog", "https://blog.example.com", "key_id:secret")
        assertTrue(result.isSuccess)
        val account = result.getOrNull()!!
        assertEquals("Personal Blog", account.name)
        assertEquals("https://blog.example.com", account.blogUrl)
        assertEquals("key_id:secret", account.apiKey)
        assertTrue(account.id.isNotBlank())
    }

    @Test
    fun `addAccount sets new account as active`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:secret1")
        val active = accountManager.getActiveAccount()
        assertNotNull(active)
        assertEquals("Blog 1", active!!.name)
    }

    @Test
    fun `addAccount with empty name uses extracted blog name`() {
        val result = accountManager.addAccount("", "https://myblog.ghost.io", "key:secret")
        assertTrue(result.isSuccess)
        assertEquals("Myblog", result.getOrNull()!!.name)
    }

    @Test
    fun `addAccount assigns unique color indices`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")
        accountManager.addAccount("Blog 3", "https://blog3.com", "key3:s3")

        val accounts = accountManager.getAccounts()
        val colors = accounts.map { it.colorIndex }.toSet()
        assertEquals("Each account should have a unique color", 3, colors.size)
    }

    @Test
    fun `addAccount generates unique ids`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        val accounts = accountManager.getAccounts()
        val ids = accounts.map { it.id }.toSet()
        assertEquals(2, ids.size)
    }

    // --- CRUD: Get Accounts ---

    @Test
    fun `getAccounts returns empty list initially`() {
        assertEquals(emptyList<GhostAccount>(), accountManager.getAccounts())
    }

    @Test
    fun `getAccounts returns all added accounts`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        val accounts = accountManager.getAccounts()
        assertEquals(2, accounts.size)
    }

    @Test
    fun `getAccounts marks active account correctly`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        val result2 = accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        val accounts = accountManager.getAccounts()
        val activeAccount = accounts.find { it.isActive }
        assertNotNull(activeAccount)
        assertEquals(result2.getOrNull()!!.id, activeAccount!!.id)
    }

    // --- CRUD: Get Active Account ---

    @Test
    fun `getActiveAccount returns null when no accounts`() {
        assertNull(accountManager.getActiveAccount())
    }

    @Test
    fun `getActiveAccount returns the set active account`() {
        val result1 = accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        accountManager.setActiveAccount(result1.getOrNull()!!.id)
        val active = accountManager.getActiveAccount()
        assertEquals("Blog 1", active!!.name)
    }

    // --- CRUD: Set Active Account ---

    @Test
    fun `setActiveAccount returns true for valid id`() {
        val result = accountManager.addAccount("Blog", "https://blog.com", "key:s")
        assertTrue(accountManager.setActiveAccount(result.getOrNull()!!.id))
    }

    @Test
    fun `setActiveAccount returns false for invalid id`() {
        assertFalse(accountManager.setActiveAccount("nonexistent-id"))
    }

    @Test
    fun `setActiveAccount switches active account`() {
        val result1 = accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        accountManager.setActiveAccount(result1.getOrNull()!!.id)
        val active = accountManager.getActiveAccount()
        assertEquals(result1.getOrNull()!!.id, active!!.id)
    }

    // --- CRUD: Remove Account ---

    @Test
    fun `removeAccount removes the specified account`() {
        val result = accountManager.addAccount("Blog", "https://blog.com", "key:s")
        assertTrue(accountManager.removeAccount(result.getOrNull()!!.id))
        assertEquals(0, accountManager.getAccounts().size)
    }

    @Test
    fun `removeAccount returns false for nonexistent id`() {
        assertFalse(accountManager.removeAccount("nonexistent"))
    }

    @Test
    fun `removeAccount switches active to next account when removing active`() {
        val result1 = accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        val result2 = accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")
        // Blog 2 is now active (most recently added)

        accountManager.removeAccount(result2.getOrNull()!!.id)
        val active = accountManager.getActiveAccount()
        assertNotNull(active)
        assertEquals(result1.getOrNull()!!.id, active!!.id)
    }

    @Test
    fun `removeAccount clears active when last account removed`() {
        val result = accountManager.addAccount("Blog", "https://blog.com", "key:s")
        accountManager.removeAccount(result.getOrNull()!!.id)
        assertNull(accountManager.getActiveAccount())
    }

    @Test
    fun `removeAccount does not change active when removing non-active`() {
        val result1 = accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        val result2 = accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")
        // Blog 2 is active

        accountManager.removeAccount(result1.getOrNull()!!.id)
        val active = accountManager.getActiveAccount()
        assertEquals(result2.getOrNull()!!.id, active!!.id)
    }

    // --- CRUD: Update Account ---

    @Test
    fun `updateAccount updates name`() {
        val result = accountManager.addAccount("Old Name", "https://blog.com", "key:s")
        assertTrue(accountManager.updateAccount(result.getOrNull()!!.id, name = "New Name"))
        val updated = accountManager.getAccounts().find { it.id == result.getOrNull()!!.id }
        assertEquals("New Name", updated!!.name)
    }

    @Test
    fun `updateAccount updates blogUrl`() {
        val result = accountManager.addAccount("Blog", "https://old.com", "key:s")
        assertTrue(accountManager.updateAccount(result.getOrNull()!!.id, blogUrl = "https://new.com"))
        val updated = accountManager.getAccounts().find { it.id == result.getOrNull()!!.id }
        assertEquals("https://new.com", updated!!.blogUrl)
    }

    @Test
    fun `updateAccount updates apiKey`() {
        val result = accountManager.addAccount("Blog", "https://blog.com", "old_key:secret")
        assertTrue(accountManager.updateAccount(result.getOrNull()!!.id, apiKey = "new_key:secret"))
        val updated = accountManager.getAccounts().find { it.id == result.getOrNull()!!.id }
        assertEquals("new_key:secret", updated!!.apiKey)
    }

    @Test
    fun `updateAccount returns false for nonexistent id`() {
        assertFalse(accountManager.updateAccount("nonexistent", name = "Name"))
    }

    @Test
    fun `updateAccount preserves unchanged fields`() {
        val result = accountManager.addAccount("Blog", "https://blog.com", "key:s")
        accountManager.updateAccount(result.getOrNull()!!.id, name = "New Name")
        val updated = accountManager.getAccounts().find { it.id == result.getOrNull()!!.id }
        assertEquals("https://blog.com", updated!!.blogUrl)
        assertEquals("key:s", updated.apiKey)
    }

    // --- Max Accounts Limit ---

    @Test
    fun `addAccount fails when max accounts reached`() {
        for (i in 1..5) {
            val r = accountManager.addAccount("Blog $i", "https://blog$i.com", "key$i:s$i")
            assertTrue("Account $i should be added successfully", r.isSuccess)
        }

        val result = accountManager.addAccount("Blog 6", "https://blog6.com", "key6:s6")
        assertTrue(result.isFailure)
        assertTrue(result.exceptionOrNull()!!.message!!.contains("Maximum"))
    }

    @Test
    fun `max accounts is 5`() {
        assertEquals(5, AccountManager.MAX_ACCOUNTS)
    }

    @Test
    fun `can add account after removing one at max`() {
        val accountIds = mutableListOf<String>()
        for (i in 1..5) {
            val r = accountManager.addAccount("Blog $i", "https://blog$i.com", "key$i:s$i")
            accountIds.add(r.getOrNull()!!.id)
        }

        accountManager.removeAccount(accountIds[0])
        val result = accountManager.addAccount("Blog 6", "https://blog6.com", "key6:s6")
        assertTrue(result.isSuccess)
    }

    // --- Migration ---

    @Test
    fun `migration from single account creates account entry`() {
        // Simulate legacy state: write directly to prefs
        prefs.edit()
            .putString("ghost_url", "https://legacy.ghost.io")
            .putString("admin_api_key", "legacy_key:legacy_secret")
            .remove("multi_account_migration_done")
            .apply()

        // Create a new AccountManager to trigger migration
        val freshManager = AccountManager(prefs)
        val accounts = freshManager.getAccounts()

        assertEquals(1, accounts.size)
        val account = accounts[0]
        assertEquals("https://legacy.ghost.io", account.blogUrl)
        assertEquals("legacy_key:legacy_secret", account.apiKey)
        assertTrue(account.isActive)
    }

    @Test
    fun `migration removes legacy keys`() {
        prefs.edit()
            .putString("ghost_url", "https://legacy.ghost.io")
            .putString("admin_api_key", "legacy_key:legacy_secret")
            .remove("multi_account_migration_done")
            .apply()

        AccountManager(prefs)
        // Legacy keys should be removed
        assertNull(prefs.getString("ghost_url", null))
        assertNull(prefs.getString("admin_api_key", null))
    }

    @Test
    fun `migration only runs once`() {
        prefs.edit()
            .putString("ghost_url", "https://legacy.ghost.io")
            .putString("admin_api_key", "legacy_key:legacy_secret")
            .remove("multi_account_migration_done")
            .apply()

        val manager1 = AccountManager(prefs)
        assertEquals(1, manager1.getAccounts().size)

        // Second instantiation should not duplicate
        val manager2 = AccountManager(prefs)
        assertEquals(1, manager2.getAccounts().size)
    }

    @Test
    fun `migration does nothing when no legacy credentials`() {
        prefs.edit()
            .remove("ghost_url")
            .remove("admin_api_key")
            .remove("multi_account_migration_done")
            .apply()

        val freshManager = AccountManager(prefs)
        assertEquals(0, freshManager.getAccounts().size)
    }

    @Test
    fun `migration extracts blog name from legacy url`() {
        prefs.edit()
            .putString("ghost_url", "https://myblog.ghost.io")
            .putString("admin_api_key", "key:secret")
            .remove("multi_account_migration_done")
            .apply()

        val freshManager = AccountManager(prefs)
        val account = freshManager.getAccounts().first()
        assertEquals("Myblog", account.name)
    }

    // --- isConfigured ---

    @Test
    fun `isConfigured returns false when no accounts`() {
        assertFalse(accountManager.isConfigured)
    }

    @Test
    fun `isConfigured returns true when active account exists`() {
        accountManager.addAccount("Blog", "https://blog.com", "key:s")
        assertTrue(accountManager.isConfigured)
    }

    // --- hasAnyAccount ---

    @Test
    fun `hasAnyAccount returns false when no accounts`() {
        assertFalse(accountManager.hasAnyAccount)
    }

    @Test
    fun `hasAnyAccount returns true when accounts exist`() {
        accountManager.addAccount("Blog", "https://blog.com", "key:s")
        assertTrue(accountManager.hasAnyAccount)
    }

    // --- clearAll ---

    @Test
    fun `clearAll removes all accounts`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        accountManager.clearAll()

        assertEquals(0, accountManager.getAccounts().size)
        assertNull(accountManager.getActiveAccount())
        assertFalse(accountManager.isConfigured)
    }

    // --- extractBlogName ---

    @Test
    fun `extractBlogName removes ghost io suffix`() {
        assertEquals("Myblog", accountManager.extractBlogName("https://myblog.ghost.io"))
    }

    @Test
    fun `extractBlogName removes com suffix`() {
        assertEquals("Myblog", accountManager.extractBlogName("https://myblog.com"))
    }

    @Test
    fun `extractBlogName handles URL without scheme`() {
        assertEquals("Myblog", accountManager.extractBlogName("myblog.ghost.io"))
    }

    @Test
    fun `extractBlogName capitalizes first letter`() {
        assertEquals("Myblog", accountManager.extractBlogName("myblog.ghost.io"))
    }

    @Test
    fun `extractBlogName handles trailing slash`() {
        assertEquals("Myblog", accountManager.extractBlogName("https://myblog.ghost.io/"))
    }

    @Test
    fun `extractBlogName handles URL with path`() {
        assertEquals("Myblog", accountManager.extractBlogName("https://myblog.ghost.io/blog"))
    }

    // --- Account switching ---

    @Test
    fun `switching active account changes getActiveAccount result`() {
        val r1 = accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        val r2 = accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        // Blog 2 is now active
        assertEquals("Blog 2", accountManager.getActiveAccount()!!.name)

        // Switch to Blog 1
        accountManager.setActiveAccount(r1.getOrNull()!!.id)
        assertEquals("Blog 1", accountManager.getActiveAccount()!!.name)

        // Switch back to Blog 2
        accountManager.setActiveAccount(r2.getOrNull()!!.id)
        assertEquals("Blog 2", accountManager.getActiveAccount()!!.name)
    }

    @Test
    fun `only one account is marked active at a time`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")
        accountManager.addAccount("Blog 3", "https://blog3.com", "key3:s3")

        val accounts = accountManager.getAccounts()
        val activeCount = accounts.count { it.isActive }
        assertEquals(1, activeCount)
    }

    @Test
    fun `persisted data survives new AccountManager instance`() {
        accountManager.addAccount("Blog 1", "https://blog1.com", "key1:s1")
        accountManager.addAccount("Blog 2", "https://blog2.com", "key2:s2")

        // Create a new instance sharing the same prefs
        val newManager = AccountManager(prefs)
        assertEquals(2, newManager.getAccounts().size)
        assertEquals("Blog 2", newManager.getActiveAccount()!!.name)
    }
}
