//! SYN-20 Token Contract
//!
//! A standard fungible token implementation for Synor, similar to ERC-20.
//!
//! # Features
//! - Minting (owner only)
//! - Burning
//! - Transfers
//! - Allowances (approve/transferFrom)
//! - Ownership management
//!
//! # Methods
//! - `init(name, symbol, decimals, initial_supply)` - Initialize token
//! - `name() -> String` - Token name
//! - `symbol() -> String` - Token symbol
//! - `decimals() -> u8` - Decimal places
//! - `total_supply() -> u64` - Total supply
//! - `balance_of(owner) -> u64` - Balance of address
//! - `transfer(to, amount) -> bool` - Transfer tokens
//! - `approve(spender, amount) -> bool` - Approve allowance
//! - `allowance(owner, spender) -> u64` - Check allowance
//! - `transfer_from(from, to, amount) -> bool` - Transfer from allowance
//! - `mint(to, amount)` - Mint new tokens (owner only)
//! - `burn(amount)` - Burn tokens

#![no_std]

extern crate alloc;

use alloc::string::String;
use alloc::vec::Vec;
use borsh::BorshDeserialize;
use synor_sdk::prelude::*;
use synor_sdk::{require, require_auth};

// ==================== Storage Keys ====================

mod keys {
    pub const NAME: &[u8] = b"syn20:name";
    pub const SYMBOL: &[u8] = b"syn20:symbol";
    pub const DECIMALS: &[u8] = b"syn20:decimals";
    pub const TOTAL_SUPPLY: &[u8] = b"syn20:total_supply";
    pub const OWNER: &[u8] = b"syn20:owner";
    pub const BALANCES: &[u8] = b"syn20:balances";
    pub const ALLOWANCES: &[u8] = b"syn20:allowances";
}

// ==================== Storage Helpers ====================

fn balances() -> storage::Map<[u8; 34], u64> {
    storage::Map::new(keys::BALANCES)
}

fn allowances_key(owner: &Address, spender: &Address) -> Vec<u8> {
    let mut key = Vec::with_capacity(68);
    key.extend_from_slice(owner.as_bytes());
    key.extend_from_slice(spender.as_bytes());
    key
}

fn get_allowance(owner: &Address, spender: &Address) -> u64 {
    let key = allowances_key(owner, spender);
    storage::get_with_suffix::<u64>(keys::ALLOWANCES, &key).unwrap_or(0)
}

fn set_allowance(owner: &Address, spender: &Address, amount: u64) {
    let key = allowances_key(owner, spender);
    storage::set_with_suffix(keys::ALLOWANCES, &key, &amount);
}

fn get_owner() -> Option<Address> {
    storage::get::<Address>(keys::OWNER)
}

// ==================== Entry Points ====================

synor_sdk::entry_point!(init, call);

/// Initialize the token contract.
fn init(params: &[u8]) -> Result<()> {
    #[derive(BorshDeserialize)]
    struct InitParams {
        name: String,
        symbol: String,
        decimals: u8,
        initial_supply: u64,
    }

    let params = InitParams::try_from_slice(params)
        .map_err(|_| Error::invalid_args("Invalid init params"))?;

    // Store token metadata
    storage::set(keys::NAME, &params.name);
    storage::set(keys::SYMBOL, &params.symbol);
    storage::set(keys::DECIMALS, &params.decimals);
    storage::set(keys::TOTAL_SUPPLY, &params.initial_supply);

    // Set owner
    let owner = caller();
    storage::set(keys::OWNER, &owner);

    // Mint initial supply to owner
    if params.initial_supply > 0 {
        balances().set(&owner.0, &params.initial_supply);

        // Emit transfer from zero address
        emit(&synor_sdk::events::Transfer {
            from: Address::zero(),
            to: owner,
            amount: params.initial_supply,
        });
    }

    Ok(())
}

/// Handle contract calls.
fn call(selector: &[u8], params: &[u8]) -> Result<Vec<u8>> {
    // Method selectors (computed at runtime)
    let name_sel = synor_sdk::method_selector("name");
    let symbol_sel = synor_sdk::method_selector("symbol");
    let decimals_sel = synor_sdk::method_selector("decimals");
    let total_supply_sel = synor_sdk::method_selector("total_supply");
    let balance_of_sel = synor_sdk::method_selector("balance_of");
    let transfer_sel = synor_sdk::method_selector("transfer");
    let approve_sel = synor_sdk::method_selector("approve");
    let allowance_sel = synor_sdk::method_selector("allowance");
    let transfer_from_sel = synor_sdk::method_selector("transfer_from");
    let mint_sel = synor_sdk::method_selector("mint");
    let burn_sel = synor_sdk::method_selector("burn");
    let owner_sel = synor_sdk::method_selector("owner");
    let transfer_ownership_sel = synor_sdk::method_selector("transfer_ownership");

    match selector {
        s if s == name_sel => {
            let name: String = storage::get(keys::NAME).unwrap_or_default();
            Ok(borsh::to_vec(&name).unwrap())
        }

        s if s == symbol_sel => {
            let symbol: String = storage::get(keys::SYMBOL).unwrap_or_default();
            Ok(borsh::to_vec(&symbol).unwrap())
        }

        s if s == decimals_sel => {
            let decimals: u8 = storage::get(keys::DECIMALS).unwrap_or(8);
            Ok(borsh::to_vec(&decimals).unwrap())
        }

        s if s == total_supply_sel => {
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            Ok(borsh::to_vec(&supply).unwrap())
        }

        s if s == balance_of_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                owner: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected owner address"))?;
            let balance = balances().get_or_default(&args.owner.0);
            Ok(borsh::to_vec(&balance).unwrap())
        }

        s if s == transfer_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                to: Address,
                amount: u64,
            }
            let args =
                Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected to, amount"))?;
            let success = do_transfer(&caller(), &args.to, args.amount)?;
            Ok(borsh::to_vec(&success).unwrap())
        }

        s if s == approve_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                spender: Address,
                amount: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected spender, amount"))?;
            let owner = caller();
            set_allowance(&owner, &args.spender, args.amount);

            emit(&synor_sdk::events::Approval {
                owner,
                spender: args.spender,
                amount: args.amount,
            });

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == allowance_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                owner: Address,
                spender: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected owner, spender"))?;
            let amount = get_allowance(&args.owner, &args.spender);
            Ok(borsh::to_vec(&amount).unwrap())
        }

        s if s == transfer_from_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                from: Address,
                to: Address,
                amount: u64,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected from, to, amount"))?;

            let spender = caller();
            let current_allowance = get_allowance(&args.from, &spender);

            require!(
                current_allowance >= args.amount,
                Error::InsufficientBalance {
                    required: args.amount,
                    available: current_allowance
                }
            );

            // Decrease allowance
            set_allowance(&args.from, &spender, current_allowance - args.amount);

            // Do transfer
            let success = do_transfer(&args.from, &args.to, args.amount)?;
            Ok(borsh::to_vec(&success).unwrap())
        }

        s if s == mint_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                to: Address,
                amount: u64,
            }
            let args =
                Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected to, amount"))?;

            // Only owner can mint
            let owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(owner);

            // Update total supply
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            let new_supply = supply.checked_add(args.amount).ok_or(Error::Overflow)?;
            storage::set(keys::TOTAL_SUPPLY, &new_supply);

            // Credit recipient
            let balance = balances().get_or_default(&args.to.0);
            let new_balance = balance.checked_add(args.amount).ok_or(Error::Overflow)?;
            balances().set(&args.to.0, &new_balance);

            emit(&synor_sdk::events::Transfer {
                from: Address::zero(),
                to: args.to,
                amount: args.amount,
            });

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == burn_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                amount: u64,
            }
            let args =
                Args::try_from_slice(params).map_err(|_| Error::invalid_args("Expected amount"))?;

            let burner = caller();
            let balance = balances().get_or_default(&burner.0);

            require!(
                balance >= args.amount,
                Error::InsufficientBalance {
                    required: args.amount,
                    available: balance
                }
            );

            // Decrease balance
            balances().set(&burner.0, &(balance - args.amount));

            // Decrease total supply
            let supply: u64 = storage::get(keys::TOTAL_SUPPLY).unwrap_or(0);
            storage::set(keys::TOTAL_SUPPLY, &supply.saturating_sub(args.amount));

            emit(&synor_sdk::events::Transfer {
                from: burner,
                to: Address::zero(),
                amount: args.amount,
            });

            Ok(borsh::to_vec(&true).unwrap())
        }

        s if s == owner_sel => {
            let owner = get_owner().unwrap_or(Address::zero());
            Ok(borsh::to_vec(&owner).unwrap())
        }

        s if s == transfer_ownership_sel => {
            #[derive(BorshDeserialize)]
            struct Args {
                new_owner: Address,
            }
            let args = Args::try_from_slice(params)
                .map_err(|_| Error::invalid_args("Expected new_owner"))?;

            let current_owner = get_owner().ok_or(Error::Unauthorized)?;
            require_auth!(current_owner);

            storage::set(keys::OWNER, &args.new_owner);

            emit(&synor_sdk::events::OwnershipTransferred {
                previous_owner: current_owner,
                new_owner: args.new_owner,
            });

            Ok(Vec::new())
        }

        _ => Err(Error::InvalidMethod),
    }
}

// ==================== Internal Functions ====================

fn do_transfer(from: &Address, to: &Address, amount: u64) -> Result<bool> {
    require!(
        *from != Address::zero(),
        Error::invalid_args("Cannot transfer from zero address")
    );
    require!(
        *to != Address::zero(),
        Error::invalid_args("Cannot transfer to zero address")
    );

    let from_balance = balances().get_or_default(&from.0);

    require!(
        from_balance >= amount,
        Error::InsufficientBalance {
            required: amount,
            available: from_balance
        }
    );

    // Update balances
    balances().set(&from.0, &(from_balance - amount));

    let to_balance = balances().get_or_default(&to.0);
    let new_to_balance = to_balance.checked_add(amount).ok_or(Error::Overflow)?;
    balances().set(&to.0, &new_to_balance);

    emit(&synor_sdk::events::Transfer {
        from: *from,
        to: *to,
        amount,
    });

    Ok(true)
}
